Baseclass, and by "mixing in" two mixins, we can create the
FinalClasswhich has the same interface but with additional features. This is accomplished through Python's powerful multiple inheritance model, which handles method dispatch by creating a method resolution order, or MRO, which is unsuprisingly a list which determines the order in which methods are called as execution proceeds through
super()calls. You can view a class' MRO as such:
add_one(), Python first checks to see if
add_one, and then
ArraysMixin, and so on and so forth. Furthermore, when
super().add_one(), Python will skip past
ArraysMixinin the MRO, first checking if
add_one, and so forth.
process(), but how do we determine what that does?
UberEngine, is defined as follows:
SuccessorsMixin, which is what provides the basic
process()implementation. This function sets up the
SimSuccessorsfor the rest of the mixins to fill in, and then calls
process_successors(), which each of the mixins which provide some mode of execution implement. If the mixin can handle the step, it does so and returns, otherwise it calls
super().process_successors(). In this way, the MRO for the engine class determines what the order of precedence for the engine's pieces is.
HeavyVEXMixin. If you look at the module hierarchy of the angr
enginessubmodule, you will see that the
vexsubmodule has a lot of pieces in it which are organized by how tightly tied to particular state types or data types they are. The heavy VEX mixin is one version of the culmination of all of these. Let's look at its definition:
VEXMixin. This mixin is designed to provide the barest-bones framework for processing a VEX block. Take a look at its source code. Its main purpose is to perform the preliminary digestion of the VEX IRSB and dispatch processing of it to methods which are provided by mixins - look at the methods which are either
return NotImplemented. Notice that absolutely none of its code makes any assumption whatsoever of what the type of
stateis or even what the type of the data words inside
stateare. This job is delegated to other mixins, making the
VEXMixinan appropriate base class for literally any analysis on VEX blocks.
ClaripyDataMixin, whose source code is here. This mixin actually integrates the fact that we are executing over the domain of Claripy ASTs. It does this by implementing some of the methods which are unimplemented in the
VEXMixin, most importantly the
ITEexpression, all the operations, and the clean helpers.
SimStateStorageMixinprovides the glue between the
VEXMixin's interface for memory writes et al and SimState's interface for memory writes and such. It is unremarkable, except for a small interaction between it and the
ClaripyDataMixin. The Claripy mixin also overrides the memory/register read/write functions, for the purpose of converting between the bitvector and floating-point types, since the vex interface expects to be able to load and store floats, but the SimState interface wants to load and store only bitvectors. Because of this, the claripy mixin must come before the storage mixin in the MRO. This is very much an interaction like the one in the add_one example at the start of this page - one mixin serves as a data filtering layer for another mixin.
HeavyVEXMixinbut rather mixed into the
UberEngineformula explicitly: the
TrackActionsMixin. This mixin implements "SimActions", which is angr parlance for dataflow tracking. Again, look at the source code. The way it does this is that it wraps and unwraps the data layer to pass around additional information about data flows. Look at how it instruments
RdTmp, for instance. It immediately
super()-calls to the next method in the MRO, but instead of returning that data it returns a tuple of the data and its dependencies, which depending on whether you want temporary variables to be atoms in the dataflow model, will either be just the tmp which was read or the dependencies of the value written to that tmp.
_handle_vex_constexpression, so immediate value loads are not annotated with dependencies. The expression value which will be returned from the mixin which does provide
_handle_vex_constwill not be a tuple of (expression, deps), it will just be the expression. Imagine this execution is taking place in the context of a
WrTmp(t0, Const(0)). The const expression will be passed down to the
WrTmphandler along with the identifier of the tmp to write to. However, since
_handle_vex_stmt_WrTmpwill be overridden by our mixin which touches the data layer, it expects to be passed the tuple including the deps, and so it will crash when trying to unpack the not-a-tuple value.