KLEE implements symbolic execution by interpreting LLVM bitcode. Symbolic memory is defined by inserting special calls to KLEE (namely klee_make_symbolic) During execution, KLEE tracks all uses of symbolic memory. Constraints on symbolic memory usage are collected. Memory that is defined using previously declared symbolic memory become symbolic as well. Whenever a branch refering to symbolic memory is encountered, KLEE forks the entire states and explores each side of the branch for which a possible solution to the symbolic constraints can be found. KLEE makes queries to STP to solve symbolic constraints.
The rest of this document describes some of the important components of KLEE
Each ExecutionState stores a mapping of MemoryObjects -> ObjectState using the AddressSpace data structure (implemented as an immutable tree so that copying is cheap and the shared structure is exploited). Each AddressSpace may "own" some subset of the ObjectStates in the mapping. When an AddressSpace is duplicated it loses ownership of the ObjectState in the map. Any subsequent write to an ObjectState will create a copy of the object (AddressSpace::getWriteable). This is the COW mechanism (which gets used for all objects, not just globals).
From the point of view of the state and this mapping there is no distinction between stack, heap, and global objects. The only special handling for stack objects is that the MemoryObject is marked as isLocal and the MemoryObject is stored in the StackFrame alloca list. When the StackFrame is popped these objects are then unbound so that the state can no longer access the memory directly (references to the memory object may still remain in ReadExprs, but conceptually the actual memory is no longer addressable).
It is also important that the AddressSpace mapping is ordered. We use this when we need to resolve a symbolic address to an ObjectState by first getting a particular value for the symbolic address, and using that value to start looking for objects that the pointer can fall within. Difference betweens MemoryObjects and ObjectStates ?
The way memory is implemented all accesses are broken down into byte level operations. This means that the memory system (by which I mean the ObjectState data structure) tends to use a lot of ExtractExpr and Concat?Expr, so it is very important that these expressions fold their operands when possible.
The ReadExpr is probably the most important one. Conceptually it is simply an index and a list of (index, value) updates (writes). The ReadExpr evaluates to all the values for which the two indices can be equal. The ObjectState structure uses a cache for concrete writes and for symbolic writes at concrete indices, but for writes at symbolic indices it must construct a list of such updates. These are stored in the UpdateList and UpdateNode structures which are again immutable data structures so that copy is cheap and the sharing is exploited.
1.5.8