<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving and Interchange DTD v1.0 20120330//EN" "JATS-archivearticle1.dtd">
<article xmlns:xlink="http://www.w3.org/1999/xlink">
  <front>
    <journal-meta />
    <article-meta>
      <title-group>
        <article-title>Boost Symbolic Execution Using Dynamic State Merging and Forking</article-title>
      </title-group>
      <contrib-group>
        <contrib contrib-type="author">
          <string-name>Chao Zhang</string-name>
          <email>zhang-chao13@mails.tsinghua.edu.cn</email>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Weiliang Yin Zhiqiang Liny</string-name>
          <email>fyinweiliang.ywliang ,linzhiqiangyg@huawei.com</email>
          <email>linzhiqiangyg@huawei.com</email>
          <xref ref-type="aff" rid="aff1">1</xref>
        </contrib>
        <aff id="aff0">
          <label>0</label>
          <institution>Beijing National Research Center for, Information Science and Technology(BNRist), School of Software, Tsinghua University</institution>
          ,
          <addr-line>Beijing 100084</addr-line>
          ,
          <country country="CN">China</country>
        </aff>
        <aff id="aff1">
          <label>1</label>
          <institution>HUAWEI</institution>
          ,
          <country country="CN">China</country>
        </aff>
      </contrib-group>
      <pub-date>
        <year>2018</year>
      </pub-date>
      <fpage>14</fpage>
      <lpage>21</lpage>
      <abstract>
        <p>-Symbolic execution has achieved wide application in software testing and analysis. However, path explosion remains the bottleneck limiting scalability of most symbolic execution engines in practice. One of the promising solutions to address this issue is to merge explored states and decrease number of paths. Nevertheless, state merging leads to increase in complexity of path predicates at the same time, especially in the situation where variables with concrete values are turned symbolic and chances of concretely executing some statements are dissipated. As a result, calculating expressions and constraints becomes much more timeconsuming and thus, the performance of symbolic execution is weakened in contrast. To resolve the problem, we propose a merge-fork framework enabling states under exploration to switch automatically between merging mode and forking mode. First, active state forking is introduced to enable forking a state into multiple ones as if a certain merging action taken before were eliminated. Second, we perform dynamic mergefork analysis to cut source code into pieces and continuously evaluate efficiency of different merging strategies for each piece. Our approach dynamically combines paths under exploration to maximize opportunities for concrete execution and ease the burden on underlying solvers. We implement the framework on the foundation of the symbolic execution engine KLEE, and conduct experiments on GNU Coreutils code using our prototype to present the effect of our proposition. Experiments show up to 30% speedup and 80% decrease in queries compared to existing works. Index Terms-State Merging, Active State Forking, Local Merge-fork Analysis</p>
      </abstract>
    </article-meta>
  </front>
  <body>
    <sec id="sec-1">
      <title>I. INTRODUCTION</title>
      <p>
        The innovation of symbolic execution has been proposed
over four decades ago [
        <xref ref-type="bibr" rid="ref1">1</xref>
        ], [
        <xref ref-type="bibr" rid="ref2">2</xref>
        ]. It shows great potential in
software analysis and testing, and underlies a growing number
of tools which are widely applied in practical cases [
        <xref ref-type="bibr" rid="ref3">3</xref>
        ]–[
        <xref ref-type="bibr" rid="ref7">7</xref>
        ]. For
example, in [
        <xref ref-type="bibr" rid="ref5">5</xref>
        ], [
        <xref ref-type="bibr" rid="ref6">6</xref>
        ], they apply symbolic execution to greatly
improve the performance of traditional fuzz testing [
        <xref ref-type="bibr" rid="ref8">8</xref>
        ], [
        <xref ref-type="bibr" rid="ref9">9</xref>
        ]. In
[
        <xref ref-type="bibr" rid="ref7">7</xref>
        ], they incorporate symbolic execution as a bridge to reduce
the false positive of static analysis [
        <xref ref-type="bibr" rid="ref10">10</xref>
        ] and the false negative
of dynamic analysis and run-time verification [
        <xref ref-type="bibr" rid="ref11">11</xref>
        ]The key
idea behind symbolic execution is to replace input values with
symbols, update symbolic expressions for program variables,
and calculate path predicates along explored execution paths
to determine their feasibility [
        <xref ref-type="bibr" rid="ref12">12</xref>
        ], [
        <xref ref-type="bibr" rid="ref13">13</xref>
        ]. In this way, symbolic
execution simulates how a program runs and explore feasible
paths in an efficient way.
      </p>
      <p>
        However on the other hand, path explosion is still the most
significant bottleneck which limits scalability of symbolic
execution. When traversing a program, each path encodes
decisions on all its historical branches and maintains them
in its path predicate [
        <xref ref-type="bibr" rid="ref14">14</xref>
        ]. Thus, the number of paths to be
explored grows exponentially with the number of branches
and exhaustively covering all paths could be extremely
timeconsuming even for a small program [
        <xref ref-type="bibr" rid="ref15">15</xref>
        ].
      </p>
      <p>
        State-of-the-art solutions to resolve path explosion mainly
aim at directly decreasing number of paths or optimizing the
process within limited time budgets. A number of articles
introduce various search heuristics to improve performance
towards particular targets. For example, ideas from random
testing and evolutionary algorithms are frequently used to
guide the search order of path exploration so that coverage
gets higher in the same time [
        <xref ref-type="bibr" rid="ref16">16</xref>
        ], [
        <xref ref-type="bibr" rid="ref17">17</xref>
        ]. The second way
is to apply some imprecise techniques from static analysis,
such as abstract interpretation and summary. These techniques
calculate approximations to simplify analysis of code and
decrease produced states [
        <xref ref-type="bibr" rid="ref18">18</xref>
        ], [
        <xref ref-type="bibr" rid="ref19">19</xref>
        ]. State merging is another way
designed to cut down number of paths to be explored. When
path exploration encounters a branch statement, there are two
succeeding states constructed and in turn the original path is
split into to two sub-paths, corresponding to true and false case
respectively. If the two states join at the same location later,
they can be merged into one again, with path predicate updated
using disjunction and variable expressions using ITE operation
[
        <xref ref-type="bibr" rid="ref20">20</xref>
        ]. Fig.1 shows an example where there are two paths in the
given program: 1 ! 2 ! 3 ! 5 ! 6 corresponding to true
case of if-statement and 1 ! 2 ! 4 ! 5 ! 6 to false
case. Succeeding states of 3 and 4 are merged at 5, with path
predicate set to n &lt; 0 _ :(n &lt; 0) or simply T RU E, and
expression of ret set to IT E(n &lt; 0; n; n).
      </p>
      <p>
        State merging is intended to decrease the number of paths
to be explored while no imprecision is introduced. Meanwhile,
it faces the challenge that path predicates and variable
expressions may get more complex. Constraints involving disjunction
and IT E (actually another form of disjunction) are extremely
unfriendly to underlying solvers [
        <xref ref-type="bibr" rid="ref21">21</xref>
        ]. Such negative impact
on performance is rather serious when expressions which can
be evaluated concretely turn symbolic after merging. Modern
symbolic execution engines commonly adopt concrete
execuint abs(int n) {
int ret = 0;
if (n &lt; 0)
      </p>
      <p>ret = -n;
else</p>
      <p>ret = n;
return ret;
}
if (n &lt; 0)
n &lt; 0, true</p>
      <p>3
ret = ‐n;
1
2
5
6
int ret = 0;
if (n &lt; 0)
n &lt; 0, false</p>
      <p>
        4
ret = n;
return ret;
tion when code to be interpreted only involves concrete values.
Improper merging actions may dismiss chances of concrete
execution and lead to extra solving cost, which eliminates the
benefit of decrease in path scale in reverse [
        <xref ref-type="bibr" rid="ref20">20</xref>
        ]. Actually, a
merging action will speed up symbolic execution or not, or
even speed down it, is determined by the future code which is
still unexplored. Kuznetsov et al. have proposed an estimation
algorithm in [
        <xref ref-type="bibr" rid="ref14">14</xref>
        ] to statically find variables frequently used
in branch conditions and prevent any merging action erasing
concrete values of these variables. In this way, state merging
is selectively performed to exclude inefficient cases. We are
inspired by this work and try to improve merging strategies
to handle more complicated situations. We build a framework
dynamically merging and forking states according to the code
under execution. During execution, combinations of paths vary
to keep chances of concrete execution while exploration of
states and paths are joint as much as possible.
      </p>
      <p>
        Contributions. In this paper, we present a framework
boosting symbolic execution by automatically switching
between merging states together and forking them separately
throughout path exploration. We implement a prototype on the
foundation of KLEE [
        <xref ref-type="bibr" rid="ref22">22</xref>
        ] and conduct experiments on GNU
Coreutils code to evaluate the method. Our main contributions
are:
      </p>
      <p>We introduce active state forking to eliminate a historical
merging action and restore the state into original separate
ones. By integrating symbolic execution with state
merging and forking, we can change the way how states are
combined and paths are explored at any moment during
execution.</p>
      <p>We present dynamic merge-fork analysis to evaluate
performance trade-offs for different path combinations.
Decisions are made by estimating chances of concrete
execution and efforts in reasoning future statements. The
source code is cut into pieces so analysis is simplified
and conclusions can be drawn in each piece locally.</p>
      <p>The remainder of this paper is organized as follows. In
Section II we have an overview of symbolic execution and
state merging. Section III introduces active state forking,
followed by description of dynamic merge-fork analysis in
Section IV. Next we display experimental evaluation of a
prototype implementing our merge-fork framework in Section
V. Finally we give our conclusion in Section VI.</p>
    </sec>
    <sec id="sec-2">
      <title>II. SYMBOLIC EXECUTION AND STATE MERGING In this section, we first present a general process of symbolic execution and then have a discussion on state merging.</title>
      <sec id="sec-2-1">
        <title>A. Classical Symbolic Execution</title>
        <p>
          Symbolic execution starts from the program entry and
keeps updating variable expressions and path predicates to
systematically explore feasible paths. In this way the executor
can reveal code reachability, find bugs or generate test inputs.
Descriptions of a general work-flow of classical symbolic
execution techniques are given in many works [
          <xref ref-type="bibr" rid="ref14">14</xref>
          ], [
          <xref ref-type="bibr" rid="ref15">15</xref>
          ].
We refer to these works and present a general algorithm in
Algorithm 1. To be concise but without loss of generality,
we consider a language made up of three kinds of statements,
i.e. assignments, conditional branches and exit statements with
exit code to indicate normal termination or failed
assertion/error. By unwinding loops and inlining function calls we can
transform a program to this form. In the paper, all programs
used to describe our method are in such form if not explained
particularly.
        </p>
        <p>In symbolic execution, a state of the program is usually
encoded as a triple (l; P; M ) where l refers to the current
program location, P to path predicate corresponding to the
path from the entry to l, and M to symbolic store mapping
from variables to their current expressions. The algorithm
keeps taking out a state from W orklist, extending the state
and pushing newly found ones back to W orklist. Call to
selectState at line 3 determines which state is chosen to
be visited next, and thus implies the searching strategies of
path exploration. The chosen state is then updated according
to the next statement just after its location (line 5-21). Call to
eval and checkCond in this procedure symbolically calculates
expressions and checks satisfiability.</p>
        <p>Code at line 22-30 of Algorithm 1 is designed for merging
states. For a newly found state s, if there is another state
s0 in W orklist sharing the same program location, s could
be merged with s0 rather than directly added to W orklist.
Definition of shouldM erge makes decisions on whether to
merge and thus realizes strategies of state merging.</p>
      </sec>
      <sec id="sec-2-2">
        <title>B. State Merging: SSE vs. DSE</title>
        <p>
          By defining selectState and shouldM erge differently, the
classical symbolic execution algorithm presented in Section
II-A acts as two extremes in regard to design space of
state merging. Dynamic symbolic execution (DSE, adopted
in [
          <xref ref-type="bibr" rid="ref3">3</xref>
          ], [
          <xref ref-type="bibr" rid="ref22">22</xref>
          ]–[
          <xref ref-type="bibr" rid="ref24">24</xref>
          ] etc.) skips merging and every execution state
encodes a unique path from the entry to its corresponding
location. Static symbolic execution (SSE) ensures all paths
joining at each junction node in the control flow structure
Algorithm 1 Classical symbolic execution
are merged completely, which is common in symbolic model
checking and verification condition generation [
          <xref ref-type="bibr" rid="ref25">25</xref>
          ], [
          <xref ref-type="bibr" rid="ref26">26</xref>
          ].
        </p>
        <p>
          SSE systematically explores all feasible control paths with
all possible input values taken into consideration [
          <xref ref-type="bibr" rid="ref20">20</xref>
          ]. It visits
states in topological order to ensure that all corresponding
states are merged at every junction node in control flow of the
program. However, its major trade-offs are great difficulties in
calculating path predicates and strict restrictions on searching
strategies. DSE techniques are then proposed to interleave
concrete and symbolic execution and focus on exploring possible
execution paths one by one. A statement can be executed
concretely rather than symbolically if all involving variables
are concrete. For an assignment, the result is calculated
directly if possible. For a conditional branch if cond goto l0,
concrete execution is performed if cond can be determined
concretely. As a result, interpreting the statement does not
need to split the current path. DSE techniques such as [
          <xref ref-type="bibr" rid="ref3">3</xref>
          ],
[
          <xref ref-type="bibr" rid="ref23">23</xref>
          ], [
          <xref ref-type="bibr" rid="ref24">24</xref>
          ] introduce concrete inputs to make analysis benefit
from concrete execution.
        </p>
        <p>
          Taking both SSE and DSE into consideration, it requires a
compromise of the two extremes to use state merging to deal
with path explosion in real code. While dynamic techniques
remain to keep symbolic execution benefiting from concrete
execution and heuristic search order, states are selectively
merged to eliminate duplicated efforts exploring paths joining
at a certain location. The work of [
          <xref ref-type="bibr" rid="ref14">14</xref>
          ] tries to introduce state
merging in DSE and switch between different searching
strategies. The executor keeps discovering merging opportunities
with positive effects on performance and prioritizes visit of
involving states. Veritesting [
          <xref ref-type="bibr" rid="ref27">27</xref>
          ] proposes combination of SSE
and DSE by changing execution mode in process. SSE makes
code analyzed one-pass instead of visiting all feasible paths
separately in DSE. In this way the executor is able to benefit
from solvers and boost path exploration.
        </p>
        <p>
          Our proposition tries to mix SSE and DSE on the foundation
of [
          <xref ref-type="bibr" rid="ref14">14</xref>
          ]. In this article, Kuznetsov et al. have constructed
a framework to estimate whether it is worth driving path
exploration towards a potential merging action. They present
a static bottom-up analysis to find variables which may be
frequently used later and suggest merging actions with no
impact on values of these variables. Path exploration is then
under control of an SSE-like module temporally instead of a
normal DSE process. We take a more complicated case into
consideration where evaluation on a merging action varies
across the code. A static decision - either merging or not
- improves efficiency for some pieces of code but burdens
performance for others. Actually, an optimal solution usually
consists of multiple parts which are drawn in different pieces
of code locally and combined together dynamically.
        </p>
      </sec>
    </sec>
    <sec id="sec-3">
      <title>III. ACTIVE STATE FORKING In this section, we depict our active state forking technique. First, we use a series of examples to explain motivation for forking and then describe definitions and method of it.</title>
      <sec id="sec-3-1">
        <title>A. Motivating Examples</title>
        <p>As is discussed in last chapter, influence of a certain
merging action varies throughout different pieces of code. We
use some example code in Fig.2 to explain the issue in detail
and present effect of active state forking. To be concise, some
function calls are not inlined.</p>
        <p>In function f 1 state merging dismisses the opportunity of
concrete execution. There are two if-blocks in f 1, namely
line 3 and line 4-5. State forks and path splits when reaching
the first if-block (this is passive state forking caused by
nonconcrete if-condition), and then two sub-paths join after it.
Expression of f lag gets different along two sub-paths, and
is instantly used in if-condition at line 4. Expression of f lag
is 1 along true case of the first if-block, 0 along false case,
and IT E(a == b; 1; 0) if states are merged before line 4. It
can be seen that both the two separate states lead concrete
execution on the second if-block since its condition can be
decided concretely, but the merged one erases concrete value
of f lag and results in passive state forking. Therefore, the
void f1(int a, int b) {
int flag = 0;
if (a == b) flag = 1;
if (flag) g1();
else g2();
exit(0);
merging action after the first if-block just makes expressions
and path predicate more complex while number of paths to
explore is not changed.</p>
        <p>In function f 2, exit(0) at line 6 in f 1 is replaced with
another function call g3(a; b). While things go the same as
for f 1, it is required to reason about call to g3 on each
path. If paths are merged before line 14, call to g3 will be
analyzed only once rather than once along each path. Hence,
it is expected that states forked in the first if-block should be
merged at the exit of the second if-block.</p>
        <p>For function f 3, call to g3 goes before the second
ifblock. As is discussed for f 1 and f 2, we can make a similar
conclusion that states forked in the first if-block should be
merged immediately to avoid repeated efforts reasoning about
call to g3, but later ”restored” to two separate paths with
concrete value for f lag along each. Common methods try
to compare advantages and disadvantages to reveal a static
decision on state merging, and our approach, in another way,
makes it possible to fork a merged state as if a historical
merging action were not taken. In f 3, paths are made to join
after line 19 and fork actively before line 21 with f lag restored
to concrete along each forked path. In this way, the benefit
of state merging is maximized while its negative impact on
concrete execution is avoid as far as possible.</p>
      </sec>
      <sec id="sec-3-2">
        <title>B. Merge and Fork States with Extended Execution Graph</title>
        <p>The motivating examples figure out that an optimal merging
strategy may promote contrast decisions for different pieces of
code. Active state forking together with state merging makes it
possible to change how paths are combined during exploration.
To maintain information of historical merging actions and
support forking states, we make extensions to the formalism
in classical symbolic execution.</p>
        <p>A DSE method as shown in Algorithm 1 conducts path
exploration by visiting execution states and creating new ones.
This process can be efficiently represented as a graph in
which nodes are execution states and edges indicate transitions
between them. Since DSE does not merge states, such a graph
would be a tree, which is so called symbolic execution tree.
Definitions of execution state and symbolic execution tree are
given in Definition 1 and 2.</p>
        <p>Definition 1: An execution state is defined as a tuple es =
(l; P; M ) where
- l 2 L is the current program location.</p>
        <p>- P 2 F ( ; Const) is the path predicate, which is a formula
over symbolic inputs and constants Const encoding all
decisions of branches along the path from entry to l.</p>
        <p>- M : V ! F ( ; Const) is the symbolic store, which
maps a program variable to the formula corresponding to its
expression.</p>
        <p>Definition 2: A symbolic execution tree is a tree T =
(ES; T ) where
- ES is the set of execution states.</p>
        <p>- T ES ES is the set of transitions between execution
states.</p>
        <p>
          Symbolic execution tree records every explored state and
path, and is widely used to characterize the process of
symbolic execution [
          <xref ref-type="bibr" rid="ref25">25</xref>
          ]. Our extended formalism is built on top
of it. While state merging is performed, an execution state
could encode multiple nodes in the corresponding symbolic
execution tree. Such relations naturally extract information of
merging actions, and thus, we introduce virtual states to hold
the similar structure. Definition 3 presents how virtual states
are defined.
        </p>
        <p>Definition 3: A virtual state is defined as a tuple vs =
(es; CV ) where
- es 2 ES is the corresponding execution state.</p>
        <p>- CV V Const is the set of variables with concrete
values together with their values.</p>
        <p>When two execution states are merged to be one, we create
virtual state for each sub-state respectively and map them
to the produced state. Every virtual state tracks its exclusive
concrete variables. Our formalism then constructs transitions
between execution states and virtual states, resulting in a
directed acyclic graph. An execution state contains data of path
predicate and symbolic store, and is used to actually execute
statement. It appears as a node in the graph only if it never
experiences merging or we are not concerned with its historical
merging actions anymore. Otherwise, its corresponding virtual
states are used instead to make up the graph. Definition 4
describes our extended execution graph formally.</p>
        <p>Definition 4: An extended execution graph is a directed
acyclic graph G = (S; T; ; ) where
- S ES [ V S is the set of states.
- T S S denotes transitions between states.
- : ES ! 2V S maps a execution state to the set of its
corresponding virtual states.</p>
        <p>- : V S ! F ( ; Const) maps a virtual state to its newly
added conditions compared to its predecessor’s path predicate.</p>
        <p>While execution states hold essential data and drive
execution to explore paths, virtual states simulate the structure of
symbolic execution tree and preserve merging history. If there
is no further operation on virtual state, our extended execution
graph would degenerate into a tree. However, virtual states
are merged and swapped for execution states when updates
on variables efface complexity introduced by merging. In the
remaining part of this section, we explain in detail how to
perform state merging and forking with our extended execution
graph.</p>
        <p>Merging. When trying to merge es1 = (l; P1; M1) and
es2 = (l; P2; M2) into es3 = (l; P1 _ P2; M ) where M (v) =
I T E(P1; M1(v); M2(v)) for each v 2 V , we first create
virtual states and then update the graph G = (S; T ; ; ).</p>
        <p>If es1 appears as a node in G (in other word es1 2 S and
(es1) = ;), we create a virtual state directly to represent
its corresponding sub-state encoded by es3: vs = (es3; CV ).
CV denotes exclusive concrete variables of this sub-state, so
it is made up of variables concrete in es1 but not in es2. That
is, CV = (M1) n (M2) where (M ) = f(v; M (v)jM (v) 2
F (Const)g.</p>
        <p>If es1 encodes multiple sub-states so that es1 = S or
2
(es1) 6= ;, we create a virtual state for every sub-state in
(es1) and add up CV of sub-state and exclusive concrete
variables of es1 to assign to newly created one.</p>
        <p>Creating virtual states for es2 is in the same way, except that
exclusive concrete variables of es2 is calculated as (M2) n
(M1).</p>
        <p>All these generated virtual states are then added to G,
together with corresponding transitions. (es3) is set to be
all these new states. of them can be simply set to empty
since there is no change between their and their predecessors’
path predicates.</p>
        <p>Update of Assignment. Updating an execution state es in G
can be done immediately. However, if es 2= S, set of concrete
variables of each virtual state vs = (es; CV ) 2 (es) requires
update. Those not concrete or not exclusively concrete
anymore should be removed from concrete variables, while those
becoming exclusively concrete should be added to concrete
variables.</p>
        <p>E.g. for assignment v := e, (v; cv ) 2 CV is removed if e
is not concrete, or it is but calculation does not involve any
concrete variables in CV . Otherwise v and its value is added
to CV if there is no (v; cv ) 2 CV , and e is concrete and
depends on certain variables and values in CV .</p>
        <p>When updating virtual states, ones might point to the
same execution state and possess equal concrete variables
and values. These states can be merged in the graph since
distinguishing such sub-states makes no sense to merging and
forking. If all virtual states of an execution state are merged,
we can swap the production for the execution state itself,
which means that all its historical merging actions do not
influence any concrete variable and hence, will never invoke
active forking.</p>
        <p>Forking. For an execution state es in G, passive forking by
conditional branch is the same as in traditional DSE.
Otherwise, forking action is applied to every sub-state vs 2 (es)
and is updated with branch decision accordingly for forked
productions.</p>
        <p>In the case of active forking aimed at eliminating historical
merging actions, the work flow gets more complicated: it is
required to partitioning sub-states, constructing path predicates
and updating expressions.</p>
        <p>The execution state es to be forked encodes multiple
substates which are represented by virtual states in (es). Active
forking is invoked because some variables are turned from
concrete to symbolic by merging actions. Thus, the first step
is to identify concerned variables. Every virtual state vs 2
(es) where these variables are concrete is separated to be a
forked state. For sake of efficiency, states with same concrete
values for these variables can be joined. In this way, all
substates of es are partitioned into several groups and each group
corresponds to a potential forked state.</p>
        <p>For each sub-state vs, its path predicate can be calculated
by backtracking to its ancestor execution state and
combining with newly added conditions denoted by along the
path. The results are used to construct path predicate for
each potential forked state and decide I T E selections of
variables expressions. The original state es is then forked
into several execution states as if some historical merging
actions were eliminated. Besides, it is remarkable in real
case that optimization of expressions and constraints based
on data structure, constant propagation, query cache and other
practical techniques is adopted to cut down efforts and boost
speed of calculation in state forking.</p>
      </sec>
    </sec>
    <sec id="sec-4">
      <title>IV. DYNAMIC MERGE-FORK ANALYSIS</title>
      <p>Active state forking makes it possible to raise dynamic
merging strategies, and next task is to identify ”good” merging
actions. In this section, we will explain how to identify
possibilities of concrete execution and how to make decisions
on merging by analyzing code piece by piece.</p>
      <sec id="sec-4-1">
        <title>A. Identify Concrete Values and Concrete Execution</title>
        <p>State merging avoids repeated efforts in analysis but may
burden underlying solvers as trade-off. Considering examples
in Fig.2, the ideal solution would be that we can selectively
merge states so that 1) variables turned symbolic are used to
calculate future if-conditions as little as possible; 2) merging
actions with no influence on future if-conditions are as many
as possible. The former prevents state merging from being
obstacle to concrete execution while the latter indicates greater
benefit from merging.</p>
        <p>
          A bottom-up estimation identifying variables frequently
used is presented in [
          <xref ref-type="bibr" rid="ref14">14</xref>
          ]. The basic idea is tracking variables
used in if-conditions along use-def chain. Our method also
pays attention to use in if-condition, but works in top-down
order instead because analysis will be break down in different
pieces of code. A list of variables with concrete values is
maintained and updated when executing. Thus, we can identify
possible concrete values and in turn, find out chances of
concrete execution.
        </p>
      </sec>
      <sec id="sec-4-2">
        <title>B. Local Merge-fork Analysis</title>
        <p>Active state forking makes it possible to redraw decisions
on merging actions, so we can cut a program into pieces
and analyze code piece by piece. The division is randomly
made under these rules: difference in scale between pieces
is limited within a proper bound; cut points are located at
exit of if-blocks, end of inlined functions, or terminators of
sequential blocks. And a piece is divided further if it contains
greatly different parts, e.g. variables used in one part are
totally disjoint with ones in another. Nevertheless, too detailed
division achieves little increase in performance while goes
time-consuming, so we limit the times of further division.</p>
        <p>After code is divided into pieces, we can make decision for
states whether they should be merged or not in a piece. There
are still situations where different parts of code have opposite
votes for a merging action in the same piece. To resolve the
issue, we propose another two types of decision: merge until
a location and merge after a location. The former performs
merging temporally and invokes analysis again later, while the
latter make a delay to merging. Because of these choices of
merging decision, analysis of how to combine paths is invoked
at junction node in control flow structure, at the beginning of
each piece, and at locations specified by ”merge until” and
”merge after” decisions.</p>
        <p>Hence, our merge-fork analysis determines locally how to
handle each merging opportunities within a piece of code. For
the simplified language used in our discussion, this process can
be implemented by counting occurrences of if-condition and in
turn estimating number of queries needed. Besides, unresolved
function call and code block difficult to reason can also be
counted to influence strategy of merging and forking. When
decided to merge two states, path exploration is temporally
taken over from searching heuristics and involving states are
prioritized to visit.</p>
      </sec>
    </sec>
    <sec id="sec-5">
      <title>V. EXPERIMENTAL EVALUATION</title>
      <p>
        In this section we illustrate some experimental evaluation on
our proposition. We build a prototype to implement our
mergefork techniques (Section V-A), and run it with Coreutils
programs to analyze performance in two scenarios: exhaustively
exploring all feasible paths (Section V-B) and incomplete
exploration within certain time budget (Section V-C). Besides,
we also make a comparison with previous work of Kuznetsov
et al. [
        <xref ref-type="bibr" rid="ref14">14</xref>
        ] (Section V-D).
      </p>
      <sec id="sec-5-1">
        <title>A. Prototype</title>
        <p>To evaluate the effect of our merge-fork framework, we
implement active state forking and dynamic merge-fork
analysis in a prototype on the foundation of the famous symbolic
execution engine KLEE. It takes LLVM bitcode compiled from
source code as input. The original executor of KLEE performs
100
10
path exploration with no approximation, and path feasibility is
checked at every conditional branch. State forking is invoked
for each non-determined branch, and states at the same control
location are never merged. KLEE guides exploration with a
number of optional search strategies including random search,
traditional breadth/depth first search, strategy prioritizing
visiting previously unexplored code, and combination of various
strategies.</p>
        <p>KLEE has built in models of execution state and symbolic
execution tree. We extend them to implement our active
state forking method. Thanks to implementation of
expressions and optimization of constraints in KLEE, it is easy
to track updates of path predicates and we could perform
state forking by directly imposing conditions of dismissed
merging action on path predicates and symbolic store. To
support dynamic merge-fork analysis, we make a call in state
selection (selectState) to an LLVM Pass constructing
concrete dependence graph and estimating merging effects locally.
If merging is suggested, search strategy is temporally fixed
to continuously extending paths to target merging location.
Otherwise, states are explored according to original search
strategies of KLEE.</p>
        <p>The original work of KLEE presents an experiment on GNU
Coreutils programs which are widely used UNIX
commandline utilities. There are 101 files in total adding up to over
70,000 lines of code. We test these programs using our
prototype and collect information from output files.</p>
      </sec>
      <sec id="sec-5-2">
        <title>B. Speedup Path Exploration</title>
        <p>Our merge-fork framework is aimed at identifying valuable
merging opportunities and conducting state merging, so we are
first concerned with the influence of our method on number
of explored paths.</p>
        <p>A direct comparison is measuring number of explored paths
with different time spent. However, the number is hard to
figure out since there are paths explored partially if exhaustive
exploration is not completed. We therefore choose to calculate
the number of paths at a specific location instead of with some
time spent during the execution. The size of symbolic inputs
is properly set to make it possible for exhaustive exploration,
so the accurate number of feasible paths of the program is
known. Then we run our prototype and count paths at given
control locations. In this way we can reveal how state merging</p>
        <p>50 500
Completion time of plain KLEE (s)
3600</p>
        <p>Files</p>
        <p>
          Fig. 7: Decrease in queries comparing with [
          <xref ref-type="bibr" rid="ref14">14</xref>
          ].
efficiently joins branches and decrease paths. It is remarkable
that paths getting through a certain location might enclose a
large gap in time.
        </p>
        <p>Fig.3 shows the growth of path exploration ratio
corresponding to program locations for 3 representative files. The final
ratio is obtained at the exit of code. It can be seen that the
effect in decreasing paths varies among the programs. While
a large part of paths are merged in echo, state merging makes
little influence on seq. The trend of growth indicates structure
of control flow and involvement of active state forking. Since
merging and forking work simultaneously, change in path
numbers appear smooth.</p>
        <p>
          The target of decreasing paths is to free underlying solvers’
burden and achieve faster execution by omitting duplicated
analysis efforts. Hence, we run Coreutils under symbolic
inputs with growing size and compare completion time of plain
KLEE and our prototype. In order to compare with [
          <xref ref-type="bibr" rid="ref14">14</xref>
          ] easily,
we draw a similar graph Fig.4 showing the result within 1 hour
time limit. The black dots correspond to experiment instances
and the gray disks indicate the relative size of the symbolic
inputs used in each instance. The result presents that in the
majority of cases, our prototype achieves positive speedup over
KLEE. The rightmost dots represent those exceed time limit
in KLEE but can be solved completely in our framework.
        </p>
      </sec>
      <sec id="sec-5-3">
        <title>C. Exploration Guided by Statement Coverage</title>
        <p>We now look into the situation where tasks can not be
finished completely. We choose a proper size of symbolic
inputs to keep the engine busy and measure statement coverage
in limited time. Plain KLEE uses coverage-oriented search
strategy to guide path exploration towards uncovered locations.
Our merge-fork framework would interrupt the process and
temporally drive exploration towards merging states. Fig.5
shows its influence on statement coverage. While state merging
saves analysis efforts, there are more paths can be visited in
some cases, which leads to increase in coverage. However,
other cases suffer from change in search order and in turn
coverage is decreased. Data in Fig.5 confirms that estimation
of merging opportunities and fast-forwarding some states do
not cause any obvious troubles to the original exploration goal
of searching heuristics.</p>
      </sec>
      <sec id="sec-5-4">
        <title>D. Compare to Existed Work</title>
        <p>
          While our proposition is on the foundation of the previous
work of Kuznetsov et al. [
          <xref ref-type="bibr" rid="ref14">14</xref>
          ], we make some comparison
with their experiment results. All the data is obtained from
their official website http://cloud9.epfl.ch. Since experimental
environment and KLEE version may vary, all the data used in
our comparison is increase/decrease over our respective KLEE
baseline.
        </p>
        <p>First we compare the completion time under the same size of
symbolic inputs. Fig.6 illustrates the result. We use completion
time of plain KLEE to normalize data of both tools and then
calculate the effect of speedup. Over 80% of the tasks get
faster with our merge-fork method and average speedup is
around 10%. Most instances with negative speedup are finished
in a rather short time (101 magnitude in seconds).</p>
        <p>We also collect queries generated during the execution.
Fig.7 shows the comparison. The number of queries is also
normalized against data from plain KLEE. It confirms the
effect of active state forking since some queries are omitted
on separate paths by concrete execution. The improvement in
performance is achieved through decreasing paths via state
merging and invoking concrete execution by restoring merged
paths when needed.</p>
      </sec>
    </sec>
    <sec id="sec-6">
      <title>VI. CONCLUSIONS</title>
      <p>In symbolic execution, state merging decreases the number
of paths but eliminates the benefit of concrete execution in
contrast. To resolve the conflict and improve the performance,
we proposed a merge-fork framework to enable a bi-directional
switch for states to be merged and forked. State merging and
active state forking can make state and path combinations fit
different code dynamically. This in turn makes it possible
to divide code into pieces and dynamically draw decision
on state merging piece by piece. Experiment on a prototype
trivially displays the potential of our approach. While
explosive development in dynamic symbolic execution and
searchbased techniques has a huge impact on classical state merging
methodology, it shows promise combining our approach with
these techniques and making state merging an efficient method
to solve practical issues in symbolic execution.</p>
    </sec>
  </body>
  <back>
    <ref-list>
      <ref id="ref1">
        <mixed-citation>
          [1]
          <string-name>
            <given-names>L. A.</given-names>
            <surname>Clarke</surname>
          </string-name>
          , “
          <article-title>A system to generate test data and symbolically execute programs</article-title>
          ,
          <source>” IEEE Transactions on software engineering, no. 3</source>
          , pp.
          <fpage>215</fpage>
          -
          <lpage>222</lpage>
          ,
          <year>1976</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref2">
        <mixed-citation>
          [2]
          <string-name>
            <given-names>J. C.</given-names>
            <surname>King</surname>
          </string-name>
          , “
          <article-title>Symbolic execution and program testing,” Communications of the ACM</article-title>
          , vol.
          <volume>19</volume>
          , no.
          <issue>7</issue>
          , pp.
          <fpage>385</fpage>
          -
          <lpage>394</lpage>
          ,
          <year>1976</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref3">
        <mixed-citation>
          [3]
          <string-name>
            <given-names>P.</given-names>
            <surname>Godefroid</surname>
          </string-name>
          ,
          <string-name>
            <given-names>N.</given-names>
            <surname>Klarlund</surname>
          </string-name>
          , and
          <string-name>
            <given-names>K.</given-names>
            <surname>Sen</surname>
          </string-name>
          , “
          <article-title>Dart: directed automated random testing,” in ACM Sigplan Notices</article-title>
          , vol.
          <volume>40</volume>
          , no. 6. ACM,
          <year>2005</year>
          , pp.
          <fpage>213</fpage>
          -
          <lpage>223</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref4">
        <mixed-citation>
          [4]
          <string-name>
            <given-names>V.</given-names>
            <surname>Chipounov</surname>
          </string-name>
          ,
          <string-name>
            <given-names>V.</given-names>
            <surname>Kuznetsov</surname>
          </string-name>
          , and G. Candea, “
          <article-title>S2e: A platform for invivo multi-path analysis of software systems</article-title>
          ,
          <source>” ACM SIGPLAN Notices</source>
          , vol.
          <volume>46</volume>
          , no.
          <issue>3</issue>
          , pp.
          <fpage>265</fpage>
          -
          <lpage>278</lpage>
          ,
          <year>2011</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref5">
        <mixed-citation>
          [5]
          <string-name>
            <given-names>M.</given-names>
            <surname>Wang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Liang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Y.</given-names>
            <surname>Chen</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Y.</given-names>
            <surname>Jiang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>X.</given-names>
            <surname>Jiao</surname>
          </string-name>
          ,
          <string-name>
            <given-names>H.</given-names>
            <surname>Liu</surname>
          </string-name>
          ,
          <string-name>
            <given-names>X.</given-names>
            <surname>Zhao</surname>
          </string-name>
          ,
          <string-name>
            <given-names>and J.</given-names>
            <surname>Sun</surname>
          </string-name>
          , “
          <article-title>Safl: increasing and accelerating testing coverage with symbolic execution and guided fuzzing</article-title>
          ,”
          <source>in Proceedings of the 40th International Conference on Software Engineering: Companion Proceeedings. ACM</source>
          ,
          <year>2018</year>
          , pp.
          <fpage>61</fpage>
          -
          <lpage>64</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref6">
        <mixed-citation>
          [6]
          <string-name>
            <given-names>J.</given-names>
            <surname>Liang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>M.</given-names>
            <surname>Wang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Y.</given-names>
            <surname>Chen</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Y.</given-names>
            <surname>Jiang</surname>
          </string-name>
          , and
          <string-name>
            <given-names>R.</given-names>
            <surname>Zhang</surname>
          </string-name>
          , “
          <article-title>Fuzz testing in practice: Obstacles and solutions</article-title>
          ,” in
          <source>2018 IEEE 25th International Conference on Software Analysis, Evolution and Reengineering</source>
          (SANER). IEEE,
          <year>2018</year>
          , pp.
          <fpage>562</fpage>
          -
          <lpage>566</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref7">
        <mixed-citation>
          [7]
          <string-name>
            <given-names>C.</given-names>
            <surname>Wang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Y.</given-names>
            <surname>Jiang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>X.</given-names>
            <surname>Zhao</surname>
          </string-name>
          ,
          <string-name>
            <given-names>X.</given-names>
            <surname>Song</surname>
          </string-name>
          ,
          <string-name>
            <given-names>M.</given-names>
            <surname>Gu</surname>
          </string-name>
          , and
          <string-name>
            <given-names>J.</given-names>
            <surname>Sun</surname>
          </string-name>
          , “
          <article-title>Weak-assert: a weakness-oriented assertion recommendation toolkit for program analysis</article-title>
          ,
          <source>” in Proceedings of the 40th International Conference on Software Engineering: Companion Proceeedings. ACM</source>
          ,
          <year>2018</year>
          , pp.
          <fpage>69</fpage>
          -
          <lpage>72</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref8">
        <mixed-citation>
          [8]
          <string-name>
            <given-names>J.</given-names>
            <surname>GLiang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>M.</given-names>
            <surname>Wang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Y.</given-names>
            <surname>Chen</surname>
          </string-name>
          , and
          <string-name>
            <given-names>Y.</given-names>
            <surname>Jiang</surname>
          </string-name>
          , “Pafl:
          <article-title>Extend fuzzing optimizations of single mode to industrial parallel mode</article-title>
          ,”
          <year>2018</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref9">
        <mixed-citation>
          [9]
          <string-name>
            <given-names>J.</given-names>
            <surname>Guo</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Y.</given-names>
            <surname>Jiang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Y.</given-names>
            <surname>Zhao</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Q.</given-names>
            <surname>Chen</surname>
          </string-name>
          , and
          <string-name>
            <given-names>J.</given-names>
            <surname>Sun</surname>
          </string-name>
          , “
          <article-title>Dlfuzz: Differential fuzzing testing of deep learning systems</article-title>
          ,”
          <year>2018</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref10">
        <mixed-citation>
          [10]
          <string-name>
            <given-names>J.</given-names>
            <surname>Gao</surname>
          </string-name>
          ,
          <string-name>
            <given-names>X.</given-names>
            <surname>Yang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Y.</given-names>
            <surname>Fu</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Y.</given-names>
            <surname>Jiang</surname>
          </string-name>
          , and
          <string-name>
            <given-names>J.</given-names>
            <surname>Sun</surname>
          </string-name>
          , “
          <article-title>Vulseeker: a semantic learning based vulnerability seeker for cross-platform binary</article-title>
          ,”
          <source>in Proceedings of the 33rd ACM/IEEE International Conference on Automated Software Engineering. ACM</source>
          ,
          <year>2018</year>
          , pp.
          <fpage>896</fpage>
          -
          <lpage>899</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref11">
        <mixed-citation>
          [11]
          <string-name>
            <given-names>Y.</given-names>
            <surname>Jiang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>H.</given-names>
            <surname>Song</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Y.</given-names>
            <surname>Yang</surname>
          </string-name>
          , H. Liu,
          <string-name>
            <given-names>M.</given-names>
            <surname>Gu</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Y.</given-names>
            <surname>Guan</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Sun</surname>
          </string-name>
          , and L. Sha, “
          <article-title>Dependable model-driven development of cps: From stateflow simulation to verified implementation</article-title>
          ,
          <source>” ACM Transactions on CyberPhysical Systems</source>
          , vol.
          <volume>3</volume>
          , no.
          <issue>1</issue>
          , p.
          <fpage>12</fpage>
          ,
          <year>2018</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref12">
        <mixed-citation>
          [12]
          <string-name>
            <given-names>C.</given-names>
            <surname>Cadar</surname>
          </string-name>
          and
          <string-name>
            <given-names>K.</given-names>
            <surname>Sen</surname>
          </string-name>
          , “
          <article-title>Symbolic execution for software testing: three decades later,” Communications of the ACM</article-title>
          , vol.
          <volume>56</volume>
          , no.
          <issue>2</issue>
          , pp.
          <fpage>82</fpage>
          -
          <lpage>90</lpage>
          ,
          <year>2013</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref13">
        <mixed-citation>
          [13]
          <string-name>
            <given-names>C.</given-names>
            <surname>Cadar</surname>
          </string-name>
          ,
          <string-name>
            <given-names>P.</given-names>
            <surname>Godefroid</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Khurshid</surname>
          </string-name>
          ,
          <string-name>
            <given-names>C. S.</given-names>
            <surname>Pa</surname>
          </string-name>
          <article-title>˘sa˘reanu, K. Sen</article-title>
          ,
          <string-name>
            <given-names>N.</given-names>
            <surname>Tillmann</surname>
          </string-name>
          , and W. Visser, “
          <article-title>Symbolic execution for software testing in practice: preliminary assessment</article-title>
          ,”
          <source>in Proceedings of the 33rd International Conference on Software Engineering. ACM</source>
          ,
          <year>2011</year>
          , pp.
          <fpage>1066</fpage>
          -
          <lpage>1071</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref14">
        <mixed-citation>
          [14]
          <string-name>
            <given-names>V.</given-names>
            <surname>Kuznetsov</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Kinder</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Bucur</surname>
          </string-name>
          , and G. Candea, “
          <article-title>Efficient state merging in symbolic execution,” Acm Sigplan Notices</article-title>
          , vol.
          <volume>47</volume>
          , no.
          <issue>6</issue>
          , pp.
          <fpage>193</fpage>
          -
          <lpage>204</lpage>
          ,
          <year>2012</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref15">
        <mixed-citation>
          [15]
          <string-name>
            <given-names>Q.</given-names>
            <surname>Yi</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Z.</given-names>
            <surname>Yang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Guo</surname>
          </string-name>
          ,
          <string-name>
            <given-names>C.</given-names>
            <surname>Wang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Liu</surname>
          </string-name>
          , and
          <string-name>
            <given-names>C.</given-names>
            <surname>Zhao</surname>
          </string-name>
          , “
          <article-title>Eliminating path redundancy via postconditioned symbolic execution</article-title>
          ,
          <source>” IEEE Transactions on Software Engineering</source>
          ,
          <year>2017</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref16">
        <mixed-citation>
          [16]
          <string-name>
            <given-names>T.</given-names>
            <surname>Xie</surname>
          </string-name>
          ,
          <string-name>
            <given-names>N.</given-names>
            <surname>Tillmann</surname>
          </string-name>
          , J. de Halleux, and W. Schulte, “
          <article-title>Fitness-guided path exploration in dynamic symbolic execution,” in Dependable Systems</article-title>
          &amp; Networks,
          <year>2009</year>
          . DSN'09. IEEE/IFIP International Conference on. IEEE,
          <year>2009</year>
          , pp.
          <fpage>359</fpage>
          -
          <lpage>368</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref17">
        <mixed-citation>
          [17]
          <string-name>
            <given-names>J.</given-names>
            <surname>Burnim</surname>
          </string-name>
          and
          <string-name>
            <given-names>K.</given-names>
            <surname>Sen</surname>
          </string-name>
          , “
          <article-title>Heuristics for scalable dynamic test generation</article-title>
          ,” in
          <source>Automated Software Engineering</source>
          ,
          <year>2008</year>
          .
          <source>ASE</source>
          <year>2008</year>
          . 23rd IEEE/ACM International Conference on. IEEE,
          <year>2008</year>
          , pp.
          <fpage>443</fpage>
          -
          <lpage>446</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref18">
        <mixed-citation>
          [18]
          <string-name>
            <given-names>H.</given-names>
            <surname>Wang</surname>
          </string-name>
          ,
          <string-name>
            <given-names>T.</given-names>
            <surname>Liu</surname>
          </string-name>
          ,
          <string-name>
            <given-names>X.</given-names>
            <surname>Guan</surname>
          </string-name>
          ,
          <string-name>
            <given-names>C.</given-names>
            <surname>Shen</surname>
          </string-name>
          ,
          <string-name>
            <given-names>Q.</given-names>
            <surname>Zheng</surname>
          </string-name>
          , and
          <string-name>
            <given-names>Z.</given-names>
            <surname>Yang</surname>
          </string-name>
          , “
          <article-title>Dependence guided symbolic execution</article-title>
          ,
          <source>” IEEE Transactions on Software Engineering</source>
          , vol.
          <volume>43</volume>
          , no.
          <issue>3</issue>
          , pp.
          <fpage>252</fpage>
          -
          <lpage>271</lpage>
          ,
          <year>2017</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref19">
        <mixed-citation>
          [19]
          <string-name>
            <given-names>T.</given-names>
            <surname>Chen</surname>
          </string-name>
          , X.-s. Zhang, S.-z. Guo, H.-y. Li, and
          <string-name>
            <given-names>Y.</given-names>
            <surname>Wu</surname>
          </string-name>
          , “
          <article-title>State of the art: Dynamic symbolic execution for automated test generation,” Future Generation Computer Systems</article-title>
          , vol.
          <volume>29</volume>
          , no.
          <issue>7</issue>
          , pp.
          <fpage>1758</fpage>
          -
          <lpage>1773</lpage>
          ,
          <year>2013</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref20">
        <mixed-citation>
          [20]
          <string-name>
            <given-names>T.</given-names>
            <surname>Hansen</surname>
          </string-name>
          ,
          <string-name>
            <given-names>P.</given-names>
            <surname>Schachte</surname>
          </string-name>
          , and
          <string-name>
            <given-names>H.</given-names>
            <surname>Søndergaard</surname>
          </string-name>
          , “
          <article-title>State joining and splitting for the symbolic execution of binaries</article-title>
          ,” in International Workshop on Runtime Verification. Springer,
          <year>2009</year>
          , pp.
          <fpage>76</fpage>
          -
          <lpage>92</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref21">
        <mixed-citation>
          [21]
          <string-name>
            <given-names>V.</given-names>
            <surname>Ganesh</surname>
          </string-name>
          and
          <string-name>
            <given-names>D. L.</given-names>
            <surname>Dill</surname>
          </string-name>
          , “
          <article-title>A decision procedure for bit-vectors and arrays</article-title>
          ,”
          <source>in Proceedings of the 19th International Conference on Computer Aided Verification, ser. CAV'07</source>
          ,
          <year>2007</year>
          , pp.
          <fpage>519</fpage>
          -
          <lpage>531</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref22">
        <mixed-citation>
          [22]
          <string-name>
            <given-names>C.</given-names>
            <surname>Cadar</surname>
          </string-name>
          ,
          <string-name>
            <given-names>D.</given-names>
            <surname>Dunbar</surname>
          </string-name>
          ,
          <string-name>
            <given-names>D. R.</given-names>
            <surname>Engler</surname>
          </string-name>
          et al., “Klee:
          <article-title>Unassisted and automatic generation of high-coverage tests for complex systems programs</article-title>
          .” in OSDI, vol.
          <volume>8</volume>
          ,
          <issue>2008</issue>
          , pp.
          <fpage>209</fpage>
          -
          <lpage>224</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref23">
        <mixed-citation>
          [23]
          <string-name>
            <given-names>C.</given-names>
            <surname>Cadar</surname>
          </string-name>
          ,
          <string-name>
            <given-names>V.</given-names>
            <surname>Ganesh</surname>
          </string-name>
          ,
          <string-name>
            <given-names>P. M.</given-names>
            <surname>Pawlowski</surname>
          </string-name>
          ,
          <string-name>
            <given-names>D. L.</given-names>
            <surname>Dill</surname>
          </string-name>
          , and
          <string-name>
            <given-names>D. R.</given-names>
            <surname>Engler</surname>
          </string-name>
          , “
          <article-title>Exe: automatically generating inputs of death,” ACM Transactions on Information and System Security (TISSEC)</article-title>
          , vol.
          <volume>12</volume>
          , no.
          <issue>2</issue>
          , p.
          <fpage>10</fpage>
          ,
          <year>2008</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref24">
        <mixed-citation>
          [24]
          <string-name>
            <given-names>P.</given-names>
            <surname>Godefroid</surname>
          </string-name>
          ,
          <string-name>
            <given-names>M. Y.</given-names>
            <surname>Levin</surname>
          </string-name>
          ,
          <string-name>
            <given-names>D. A.</given-names>
            <surname>Molnar</surname>
          </string-name>
          et al.,
          <source>“Automated whitebox fuzz testing.” in NDSS</source>
          , vol.
          <volume>8</volume>
          ,
          <issue>2008</issue>
          , pp.
          <fpage>151</fpage>
          -
          <lpage>166</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref25">
        <mixed-citation>
          [25]
          <string-name>
            <given-names>S.</given-names>
            <surname>Khurshid</surname>
          </string-name>
          , C. S. Pa˘sa˘reanu, and W. Visser, “
          <article-title>Generalized symbolic execution for model checking</article-title>
          and testing,” in International Conference on Tools and
          <article-title>Algorithms for the Construction and Analysis of Systems</article-title>
          . Springer,
          <year>2003</year>
          , pp.
          <fpage>553</fpage>
          -
          <lpage>568</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref26">
        <mixed-citation>
          [26]
          <string-name>
            <given-names>E. M.</given-names>
            <surname>Clarke</surname>
          </string-name>
          ,
          <string-name>
            <given-names>D.</given-names>
            <surname>Kroening</surname>
          </string-name>
          , and
          <string-name>
            <given-names>F.</given-names>
            <surname>Lerda</surname>
          </string-name>
          , “
          <article-title>A tool for checking ansic programs,” tools and algorithms for construction and analysis of systems</article-title>
          , vol.
          <volume>2988</volume>
          , pp.
          <fpage>168</fpage>
          -
          <lpage>176</lpage>
          ,
          <year>2004</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref27">
        <mixed-citation>
          [27]
          <string-name>
            <given-names>T.</given-names>
            <surname>Avgerinos</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Rebert</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S. K.</given-names>
            <surname>Cha</surname>
          </string-name>
          , and
          <string-name>
            <given-names>D.</given-names>
            <surname>Brumley</surname>
          </string-name>
          , “
          <article-title>Enhancing symbolic execution with veritesting</article-title>
          ,
          <source>” in Proceedings of the 36th International Conference on Software Engineering. ACM</source>
          ,
          <year>2014</year>
          , pp.
          <fpage>1083</fpage>
          -
          <lpage>1094</lpage>
          .
        </mixed-citation>
      </ref>
    </ref-list>
  </back>
</article>