mirror of
https://github.com/vtil-project/VTIL-Core
synced 2026-08-17 08:23:03 -04:00
Shifted all tracing logic to VTIL-Arch and merged headers.
This commit is contained in:
parent
781dbbad36
commit
075679ba71
16 changed files with 828 additions and 943 deletions
|
|
@ -15,7 +15,6 @@
|
|||
<ClInclude Include="arch\instruction_set.hpp" />
|
||||
<ClInclude Include="arch\operands.hpp" />
|
||||
<ClInclude Include="arch\register_desc.hpp" />
|
||||
<ClInclude Include="includes\vtil\vm" />
|
||||
<ClInclude Include="misc\debug.hpp" />
|
||||
<ClInclude Include="routine\basic_block.hpp" />
|
||||
<ClInclude Include="routine\instruction.hpp" />
|
||||
|
|
@ -24,6 +23,9 @@
|
|||
<ClInclude Include="symex\memory.hpp" />
|
||||
<ClInclude Include="symex\pointer.hpp" />
|
||||
<ClInclude Include="symex\variable.hpp" />
|
||||
<ClInclude Include="trace\auxiliaries.hpp" />
|
||||
<ClInclude Include="trace\cached_tracer.hpp" />
|
||||
<ClInclude Include="trace\tracer.hpp" />
|
||||
<ClInclude Include="vm\lambda.hpp" />
|
||||
<ClInclude Include="vm\symbolic.hpp" />
|
||||
<ClInclude Include="vm\interface.hpp" />
|
||||
|
|
@ -36,6 +38,9 @@
|
|||
<ClCompile Include="routine\serialization.cpp" />
|
||||
<ClCompile Include="symex\pointer.cpp" />
|
||||
<ClCompile Include="symex\variable.cpp" />
|
||||
<ClCompile Include="trace\auxiliaries.cpp" />
|
||||
<ClCompile Include="trace\cached_tracer.cpp" />
|
||||
<ClCompile Include="trace\tracer.cpp" />
|
||||
<ClCompile Include="vm\symbolic.cpp" />
|
||||
<ClCompile Include="vm\interface.cpp" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@
|
|||
<Filter Include="SymEx Integration">
|
||||
<UniqueIdentifier>{b070980d-a30d-421e-82c6-0298cfe01c0e}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Value Tracing">
|
||||
<UniqueIdentifier>{6ba6b482-58aa-46cd-ad58-c0eef36c9333}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="arch\operands.hpp">
|
||||
|
|
@ -63,12 +66,18 @@
|
|||
<ClInclude Include="vm\symbolic.hpp">
|
||||
<Filter>Virtual Machine</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="includes\vtil\vm">
|
||||
<Filter>Includes</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="vm\lambda.hpp">
|
||||
<Filter>Virtual Machine</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="trace\tracer.hpp">
|
||||
<Filter>Value Tracing</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="trace\auxiliaries.hpp">
|
||||
<Filter>Value Tracing</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="trace\cached_tracer.hpp">
|
||||
<Filter>Value Tracing</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="arch\instruction_desc.cpp">
|
||||
|
|
@ -98,6 +107,15 @@
|
|||
<ClCompile Include="vm\symbolic.cpp">
|
||||
<Filter>Virtual Machine</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="trace\tracer.cpp">
|
||||
<Filter>Value Tracing</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="trace\auxiliaries.cpp">
|
||||
<Filter>Value Tracing</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="trace\cached_tracer.cpp">
|
||||
<Filter>Value Tracing</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="VTIL-Architecture.licenseheader" />
|
||||
|
|
|
|||
|
|
@ -7,4 +7,10 @@
|
|||
#include "../../routine/routine.hpp"
|
||||
#include "../../routine/basic_block.hpp"
|
||||
#include "../../routine/instruction.hpp"
|
||||
#include "../../routine/serialization.hpp"
|
||||
#include "../../routine/serialization.hpp"
|
||||
#include "../../vm/interface.hpp"
|
||||
#include "../../vm/symbolic.hpp"
|
||||
#include "../../vm/lambda.hpp"
|
||||
#include "../../trace/auxiliaries.hpp"
|
||||
#include "../../trace/tracer.hpp"
|
||||
#include "../../trace/cached_tracer.hpp"
|
||||
3
VTIL-Architecture/includes/vtil/trace
Normal file
3
VTIL-Architecture/includes/vtil/trace
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#include "../../trace/auxiliaries.hpp"
|
||||
#include "../../trace/tracer.hpp"
|
||||
#include "../../trace/cached_tracer.hpp"
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#include "../../vm/interface.hpp"
|
||||
#include "../../vm/symbolic.hpp"
|
||||
#include "../../vm/lambda.hpp"
|
||||
155
VTIL-Architecture/trace/auxiliaries.cpp
Normal file
155
VTIL-Architecture/trace/auxiliaries.cpp
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
#include "auxiliaries.hpp"
|
||||
|
||||
namespace vtil
|
||||
{
|
||||
// Checks if the instruction given accesses the variable, optionally filtering to the
|
||||
// access type specified, tracer passed will be used to generate pointers when needed.
|
||||
//
|
||||
access_details test_access( const il_const_iterator& it, const symbolic::variable::descriptor_t& var, tracer* tracer, access_type type )
|
||||
{
|
||||
// If variable is of register type:
|
||||
//
|
||||
if ( auto reg = std::get_if<symbolic::variable::register_t>( &var ) )
|
||||
{
|
||||
// Iterate each operand:
|
||||
//
|
||||
for ( int i = 0; i < it->base->operand_count(); i++ )
|
||||
{
|
||||
// Skip if not register.
|
||||
//
|
||||
if ( !it->operands[ i ].is_register() )
|
||||
continue;
|
||||
|
||||
// Skip if access type does not match.
|
||||
//
|
||||
switch ( type )
|
||||
{
|
||||
// ::read will filter to read or read/write.
|
||||
//
|
||||
case access_type::read:
|
||||
if ( it->base->operand_types[ i ] == operand_type::write )
|
||||
continue;
|
||||
break;
|
||||
// ::write will filter to write or read/write.
|
||||
//
|
||||
case access_type::write:
|
||||
if ( it->base->operand_types[ i ] < operand_type::write )
|
||||
continue;
|
||||
break;
|
||||
// ::readwrite will filter to only read/write.
|
||||
//
|
||||
case access_type::readwrite:
|
||||
if ( it->base->operand_types[ i ] != operand_type::readwrite )
|
||||
continue;
|
||||
break;
|
||||
// ::none accepts any access.
|
||||
//
|
||||
case access_type::none:
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip if no overlap.
|
||||
//
|
||||
auto& ref_reg = it->operands[ i ].reg();
|
||||
if ( !ref_reg.overlaps( *reg ) )
|
||||
continue;
|
||||
|
||||
// Return access details.
|
||||
//
|
||||
access_type type_found;
|
||||
if ( it->base->operand_types[ i ] == operand_type::readwrite )
|
||||
type_found = access_type::readwrite;
|
||||
else if ( it->base->operand_types[ i ] == operand_type::write )
|
||||
type_found = access_type::write;
|
||||
else
|
||||
type_found = access_type::read;
|
||||
|
||||
return {
|
||||
type_found,
|
||||
ref_reg.bit_offset - reg->bit_offset,
|
||||
ref_reg.bit_count
|
||||
};
|
||||
}
|
||||
}
|
||||
// If variable is of memory type:
|
||||
//
|
||||
else if( auto mem = std::get_if<symbolic::variable::memory_t>( &var ) )
|
||||
{
|
||||
// If instruction accesses memory:
|
||||
//
|
||||
if ( it->base->accesses_memory() )
|
||||
{
|
||||
// Skip if access type does not match.
|
||||
//
|
||||
switch ( type )
|
||||
{
|
||||
// ::read will filter to read.
|
||||
//
|
||||
case access_type::read:
|
||||
if ( it->base->writes_memory() )
|
||||
return { access_type::none };
|
||||
break;
|
||||
// ::write will filter to write.
|
||||
//
|
||||
case access_type::write:
|
||||
if ( !it->base->writes_memory() )
|
||||
return { access_type::none };
|
||||
break;
|
||||
// Read/write does not exist for memory operations.
|
||||
//
|
||||
case access_type::readwrite:
|
||||
unreachable();
|
||||
// ::none accepts any access.
|
||||
//
|
||||
case access_type::none:
|
||||
// Determine the type and set it.
|
||||
//
|
||||
type = it->base->writes_memory() ? access_type::write : access_type::read;
|
||||
break;
|
||||
}
|
||||
|
||||
// Generate an expression for the pointer.
|
||||
//
|
||||
auto [base, offset] = it->get_mem_loc();
|
||||
symbolic::pointer ptr = { tracer->trace( { it, base } ) + offset };
|
||||
|
||||
// If the two pointers can overlap (not restrict qualified against each other):
|
||||
//
|
||||
if ( ptr.can_overlap( mem->base ) )
|
||||
{
|
||||
// If it can be expressed as a constant:
|
||||
//
|
||||
if ( auto disp = ( ptr - mem->base ) )
|
||||
{
|
||||
// Check if within boundaries:
|
||||
//
|
||||
int64_t low_offset = *disp;
|
||||
int64_t high_offset = low_offset + it->access_size();
|
||||
if ( low_offset < ( mem->bit_count / 8 ) && high_offset > 0 )
|
||||
{
|
||||
// Can safely multiply by 8 and shrink to bitcnt_t type from int64_t
|
||||
// since variables are of maximum 64-bit size which means both offset
|
||||
// and size will be small numbers.
|
||||
//
|
||||
return {
|
||||
type,
|
||||
bitcnt_t( low_offset * 8 ),
|
||||
bitcnt_t( ( high_offset - low_offset ) * 8 )
|
||||
};
|
||||
}
|
||||
}
|
||||
// Otherwise, return unknown.
|
||||
//
|
||||
else
|
||||
{
|
||||
return { type, 0, -1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No access case.
|
||||
//
|
||||
return { access_type::none };
|
||||
}
|
||||
};
|
||||
|
|
@ -26,18 +26,14 @@
|
|||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#pragma once
|
||||
#include <vtil/arch>
|
||||
#include <vtil/symex>
|
||||
#include <vtil/io>
|
||||
#include "trace.hpp"
|
||||
#include <vtil/vm>
|
||||
#include "tracer.hpp"
|
||||
#include "../routine/basic_block.hpp"
|
||||
#include "../symex/variable.hpp"
|
||||
|
||||
namespace vtil::optimizer
|
||||
namespace vtil
|
||||
{
|
||||
// Callback typedefs.
|
||||
//
|
||||
using partial_tracer_t = std::function<symbolic::expression( bitcnt_t offset, bitcnt_t size )>;
|
||||
|
||||
// Enumeration used to describe the type of access to a variable.
|
||||
//
|
||||
enum class access_type
|
||||
|
|
@ -74,24 +70,10 @@ namespace vtil::optimizer
|
|||
bool is_unknown() const { return bit_count == -1; }
|
||||
};
|
||||
|
||||
// Makes a memory variable from the given instruction's src/dst, uses the tracer
|
||||
// passed to resolve the absolute pointer.
|
||||
//
|
||||
symbolic::variable reference_memory( const il_const_iterator& it,
|
||||
const trace_function_t& tracer = [ ] ( auto x ) { return trace( x ); } );
|
||||
|
||||
// Checks if the instruction given accesses the variable, optionally filtering to the
|
||||
// access type specified, tracer passed will be used to generate pointers when needed.
|
||||
//
|
||||
access_details test_access( const il_const_iterator& it,
|
||||
const symbolic::variable::descriptor_t& var,
|
||||
access_type type = access_type::none,
|
||||
const trace_function_t& tracer = [ ] ( auto x ) { return trace( x ); } );
|
||||
|
||||
// Given a partial tracer, this routine will determine the full value of the variable
|
||||
// at the given position where a partial write was found.
|
||||
//
|
||||
symbolic::expression resolve_partial( const access_details& access,
|
||||
bitcnt_t bit_count,
|
||||
const partial_tracer_t& ptracer );
|
||||
tracer* tracer, access_type type = access_type::none );
|
||||
};
|
||||
109
VTIL-Architecture/trace/cached_tracer.cpp
Normal file
109
VTIL-Architecture/trace/cached_tracer.cpp
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
#include "cached_tracer.hpp"
|
||||
|
||||
namespace vtil
|
||||
{
|
||||
// Hooks default tracer and does a cache lookup before invokation.
|
||||
//
|
||||
symbolic::expression cached_tracer::trace( symbolic::variable lookup )
|
||||
{
|
||||
using namespace logger;
|
||||
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the beginning of the trace.
|
||||
//
|
||||
log<CON_BRG>( "CcTrace(%s)\n", lookup );
|
||||
scope_padding _p( 1 );
|
||||
#endif
|
||||
// Handle base case.
|
||||
//
|
||||
if ( lookup.at.is_begin() )
|
||||
{
|
||||
symbolic::expression result = lookup.to_expression();
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log result.
|
||||
//
|
||||
log<CON_BRG>( "= %s [Base case]\n", result );
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
// Try lookup the exact variable in the map in a fast manner.
|
||||
//
|
||||
auto it = cache.find( lookup );
|
||||
if ( it != cache.end() )
|
||||
{
|
||||
const symbolic::expression& result = *it->second;
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log result.
|
||||
//
|
||||
log<CON_BLU>( "= %s [Cached result]\n", result );
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
// Declare a predicate for the search of the variable in the cache.
|
||||
//
|
||||
std::function<bool( const cache_entry& )> predicate;
|
||||
|
||||
// If memory variable:
|
||||
//
|
||||
if ( lookup.is_memory() )
|
||||
{
|
||||
predicate = [ & ] ( const cache_entry& pair )
|
||||
{
|
||||
// Key must be of memory type at the same position.
|
||||
//
|
||||
if ( !pair.first.is_memory() ) return false;
|
||||
if ( pair.first.at != lookup.at ) return false;
|
||||
|
||||
// Must be the same pointer and have a larger or equal size.
|
||||
//
|
||||
auto& self = lookup.mem();
|
||||
auto& other = pair.first.mem();
|
||||
return self.decay().equals( other.decay() ) &&
|
||||
self.bit_count >= other.bit_count;
|
||||
};
|
||||
}
|
||||
// If register variable:
|
||||
//
|
||||
else
|
||||
{
|
||||
fassert( lookup.is_register() );
|
||||
predicate = [ & ] ( const cache_entry& pair )
|
||||
{
|
||||
// Key must be of memory type at the same position.
|
||||
//
|
||||
if ( !pair.first.is_register() ) return false;
|
||||
if ( pair.first.at != lookup.at ) return false;
|
||||
|
||||
// Must be the same register and have a larger or equal size.
|
||||
//
|
||||
auto& self = lookup.reg();
|
||||
auto& other = pair.first.reg();
|
||||
return self.flags == other.flags &&
|
||||
self.local_id == other.local_id &&
|
||||
self.bit_offset == other.bit_offset &&
|
||||
self.bit_count >= other.bit_count;
|
||||
};
|
||||
}
|
||||
|
||||
// Search the map, if we find a matching entry shrink and use as the result.
|
||||
//
|
||||
symbolic::expression result;
|
||||
it = std::find_if( cache.begin(), cache.end(), predicate );
|
||||
if ( it != cache.end() )
|
||||
result = symbolic::expression{ *it->second }.resize( lookup.bit_count() );
|
||||
else
|
||||
result = tracer::trace( lookup );
|
||||
|
||||
// Insert a cache entry for the exact variable we're looking up and return.
|
||||
//
|
||||
cache.emplace( lookup, result );
|
||||
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log result.
|
||||
//
|
||||
log<CON_BRG>( "= %s\n", result );
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
|
@ -26,19 +26,18 @@
|
|||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <vtil/vm>
|
||||
#include "trace.hpp"
|
||||
#include <unordered_map>
|
||||
#include <vtil/utility>
|
||||
#include "tracer.hpp"
|
||||
|
||||
namespace vtil::optimizer
|
||||
namespace vtil
|
||||
{
|
||||
// Tracing is extremely costy and adding a simple cache reduces the cost
|
||||
// by ~100x fold, however we can't use a global cache since the optimizer
|
||||
// will change the instruction stream and all cache will be eventually
|
||||
// invalidated after each optimization pass so we use an instanced cache.
|
||||
//
|
||||
struct cached_tracer
|
||||
struct cached_tracer : tracer
|
||||
{
|
||||
// Define the type of the cache.
|
||||
//
|
||||
|
|
@ -50,21 +49,12 @@ namespace vtil::optimizer
|
|||
//
|
||||
cache_type cache;
|
||||
|
||||
// Replicate trace_basic with the addition of a cache lookup.
|
||||
// Hooks default tracer and does a cache lookup before invokation.
|
||||
//
|
||||
symbolic::expression trace_basic_cached( const symbolic::variable& lookup, const trace_function_t& tracer = {} );
|
||||
|
||||
// Wrappers of trace and rtrace with cached basic tracer.
|
||||
//
|
||||
symbolic::expression trace( const symbolic::variable& lookup, bool pack = true );
|
||||
symbolic::expression rtrace( const symbolic::variable& lookup, bool pack = true );
|
||||
symbolic::expression trace( symbolic::variable lookup ) override;
|
||||
|
||||
// Flushes the cache.
|
||||
//
|
||||
auto flush() { cache.clear(); return *this; }
|
||||
|
||||
// Implicit casting to a trace function.
|
||||
//
|
||||
operator trace_function_t() { return [ this ] ( auto v ) { return trace_basic_cached( v ); }; }
|
||||
};
|
||||
};
|
||||
455
VTIL-Architecture/trace/tracer.cpp
Normal file
455
VTIL-Architecture/trace/tracer.cpp
Normal file
|
|
@ -0,0 +1,455 @@
|
|||
#include "tracer.hpp"
|
||||
#include <vtil/io>
|
||||
#include "../vm/lambda.hpp"
|
||||
#include "auxiliaries.hpp"
|
||||
|
||||
namespace vtil
|
||||
{
|
||||
// Internal type definitions.
|
||||
//
|
||||
using partial_tracer_t = std::function<symbolic::expression( bitcnt_t offset, bitcnt_t size )>;
|
||||
using path_history_t = std::map<std::pair<const basic_block*, const basic_block*>, uint32_t>;
|
||||
|
||||
// Given a partial tracer, this routine will determine the full value of the variable
|
||||
// at the given position where a partial write was found.
|
||||
//
|
||||
static symbolic::expression resolve_partial( const access_details& access, bitcnt_t bit_count, const partial_tracer_t& ptracer )
|
||||
{
|
||||
using namespace logger;
|
||||
|
||||
// Fetch the result of this operation.
|
||||
//
|
||||
symbolic::expression base = ptracer( access.bit_offset, access.bit_count );
|
||||
|
||||
// Trace a low part if we have to.
|
||||
//
|
||||
if ( access.bit_offset > 0 )
|
||||
{
|
||||
bitcnt_t low_bcnt = access.bit_offset;
|
||||
auto res = ptracer( 0, low_bcnt );
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the low and middle bits.
|
||||
//
|
||||
log<CON_RED>( "dst[00..%02d] := %s\n", low_bcnt, res );
|
||||
log<CON_YLW>( "dst[%02d..%02d] := %s\n", access.bit_offset, access.bit_offset + access.bit_count, base );
|
||||
#endif
|
||||
base = res | ( base.resize( bit_count ) << low_bcnt );
|
||||
}
|
||||
// Shift the result if we have to.
|
||||
//
|
||||
else if ( access.bit_offset < 0 )
|
||||
{
|
||||
base = ( base >> access.bit_offset ).resize( bit_count );
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the low bits after shifting.
|
||||
//
|
||||
log<CON_YLW>( "dst[00..%02d] := %s\n", access.bit_offset + access.bit_count, base );
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the low bits.
|
||||
//
|
||||
log<CON_YLW>( "dst[00..%02d] := %s\n", access.bit_offset + access.bit_count, base );
|
||||
#endif
|
||||
}
|
||||
|
||||
// Trace a high part if we have to.
|
||||
//
|
||||
if ( bit_count > ( access.bit_offset + access.bit_count ) )
|
||||
{
|
||||
bitcnt_t high_bnct = bit_count - ( access.bit_offset + access.bit_count );
|
||||
auto res = ptracer( access.bit_offset + access.bit_count, high_bnct );
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the high bits.
|
||||
//
|
||||
log<CON_PRP>( "dst[%02d..%02d] := %s\n", access.bit_offset + access.bit_count, bit_count, res );
|
||||
#endif
|
||||
base = base | ( res.resize( bit_count ) << ( access.bit_offset + access.bit_count ) );
|
||||
}
|
||||
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the final result.
|
||||
//
|
||||
log<CON_GRN>( "dst := %s\n", base );
|
||||
#endif
|
||||
// Resize and return.
|
||||
//
|
||||
return base.resize( bit_count );
|
||||
}
|
||||
|
||||
// Propagates all variables in the reference expression onto the new iterator, if no
|
||||
// history pointer given will do trace instead of rtrace.
|
||||
// - Note: New iterator should be a connected block's end.
|
||||
//
|
||||
static symbolic::expression propagate( const symbolic::expression& ref, const il_const_iterator& it, tracer* tracer, path_history_t* history )
|
||||
{
|
||||
using namespace logger;
|
||||
scope_padding _p( 3 );
|
||||
|
||||
// Copy the reference expression.
|
||||
//
|
||||
symbolic::expression exp = ref;
|
||||
|
||||
// For each unique variable:
|
||||
//
|
||||
std::set<symbolic::unique_identifier> variables;
|
||||
ref.count_unique_variables( &variables );
|
||||
for ( auto& uid : variables )
|
||||
{
|
||||
// Move the variable to reference the previous block.
|
||||
//
|
||||
symbolic::variable var = uid.get<symbolic::variable>();
|
||||
|
||||
// Skip if variable is position indepdendent or not at the beginning of the block.
|
||||
//
|
||||
if ( !var.at.is_valid() || !var.at.is_begin() )
|
||||
continue;
|
||||
|
||||
// If register:
|
||||
//
|
||||
if ( var.is_register() )
|
||||
{
|
||||
// Local temporary must not exist in an expression being propagated
|
||||
// from the beginning of the block as that indicates use before assignment.
|
||||
// Make sure this is not the case.
|
||||
//
|
||||
if ( var.reg().flags & register_local )
|
||||
error( "Local variable %s is used before value assignment.\n", var );
|
||||
|
||||
// If volatile iterator cannot be moved, skip.
|
||||
//
|
||||
if ( var.reg().flags & register_volatile )
|
||||
continue;
|
||||
}
|
||||
// If memory, propagate the pointer.
|
||||
//
|
||||
else if ( var.is_memory() )
|
||||
{
|
||||
auto& pointer = var.mem().decay();
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log original pointer.
|
||||
//
|
||||
log<CON_PRP>( "Propagating pointer: %s\n", pointer->to_string() );
|
||||
#endif
|
||||
// Fail if propagation fails.
|
||||
//
|
||||
if ( !( pointer = propagate( pointer, it, tracer, nullptr ) ) )
|
||||
return {};
|
||||
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log new pointer.
|
||||
//
|
||||
log<CON_PRP>( "Pointer' => %s\n", pointer->to_string() );
|
||||
#endif
|
||||
}
|
||||
|
||||
// Move the assigned iterator.
|
||||
//
|
||||
var.at = it;
|
||||
|
||||
// Trace the variable in the destination block, fail if it fails.
|
||||
//
|
||||
symbolic::expression var_traced = history ? tracer->rtrace( var ) : tracer->trace( var );
|
||||
if ( !var_traced )
|
||||
return {};
|
||||
|
||||
// If we are tracing the value of RSP, add the stack pointer delta between blocks.
|
||||
//
|
||||
if ( var.is_register() && var.reg().is_stack_pointer() )
|
||||
var_traced = var_traced + it.container->sp_offset;
|
||||
|
||||
// Rewrite the expression
|
||||
//
|
||||
exp.transform( [ & ] ( symbolic::expression& exp )
|
||||
{
|
||||
if ( exp.is_variable() && exp.uid == uid )
|
||||
exp = var_traced;
|
||||
} );
|
||||
}
|
||||
|
||||
// Return the result.
|
||||
//
|
||||
return exp;
|
||||
}
|
||||
|
||||
// Internal implementation of ::trace with a path history.
|
||||
//
|
||||
static symbolic::expression rtrace_primitive( const symbolic::variable& lookup, tracer* tracer, const path_history_t& history )
|
||||
{
|
||||
using namespace logger;
|
||||
|
||||
// Trace through the current block first.
|
||||
//
|
||||
symbolic::expression result = tracer->trace( lookup );
|
||||
|
||||
// If result has any variables:
|
||||
//
|
||||
if ( result.count_unique_variables() != 0 )
|
||||
{
|
||||
// Determine the paths we can take to iterate further.
|
||||
//
|
||||
std::vector it_list = lookup.at.is_valid()
|
||||
? lookup.at.recurse( false )
|
||||
: std::vector<il_const_iterator>{};
|
||||
|
||||
// If there are paths take.
|
||||
//
|
||||
if ( !it_list.empty() )
|
||||
{
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log recursive tracing of the expression.
|
||||
//
|
||||
log<CON_GRN>( "Base case: %s\n", result );
|
||||
#endif
|
||||
// Save current result as default result and clear it.
|
||||
//
|
||||
symbolic::expression default_result = {};
|
||||
std::swap( result, default_result );
|
||||
|
||||
// For each path:
|
||||
//
|
||||
for ( auto& it : it_list )
|
||||
{
|
||||
// Create a local copy for the visited list for this path
|
||||
// and increment the visit counter.
|
||||
//
|
||||
path_history_t history_local = { history };
|
||||
uint32_t& visit_counter = history_local[ { lookup.at.container, it.container } ];
|
||||
|
||||
// If we've taken this path more than twice, skip it.
|
||||
//
|
||||
if ( ++visit_counter > 2 )
|
||||
{
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log skipping of path.
|
||||
//
|
||||
log<CON_CYN>( "Path [%llx->%llx] is not taken as it's n-looping.\n", lookup.at.container->entry_vip, it.container->entry_vip );
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log tracing of path.
|
||||
//
|
||||
log<CON_YLW>( "Taking path [%llx->%llx]\n", lookup.at.container->entry_vip, it.container->entry_vip );
|
||||
#endif
|
||||
// Propagate each variable onto to the destination block.
|
||||
//
|
||||
symbolic::expression exp = propagate( default_result, it, tracer, &history_local );
|
||||
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log result.
|
||||
//
|
||||
log<CON_BLU>( "= %s\n", exp );
|
||||
#endif
|
||||
// If no result is set yet, assign the current expression.
|
||||
//
|
||||
if ( !result.is_valid() )
|
||||
result = exp;
|
||||
|
||||
// If expression is invalid or not equal to previous result, fail.
|
||||
//
|
||||
if ( !exp.is_valid() || !exp.equals( result ) )
|
||||
{
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log decision.
|
||||
//
|
||||
log<CON_RED>( "Halting tracer as it was not deterministic.\n" );
|
||||
#endif
|
||||
// If result was null, return lookup.
|
||||
//
|
||||
if ( !exp.is_valid() )
|
||||
{
|
||||
result = lookup.to_expression();
|
||||
}
|
||||
// If it was mismatchign, return default result as branch dependant.
|
||||
//
|
||||
else
|
||||
{
|
||||
result = default_result;
|
||||
result.transform( [ ] ( symbolic::expression& exp )
|
||||
{
|
||||
if ( exp.is_variable() )
|
||||
exp.uid.get<symbolic::variable>().is_branch_dependant = true;
|
||||
}, false );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log result.
|
||||
//
|
||||
log<CON_BRG>( "= %s\n", result );
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
// Traces a variable across the basic block it belongs to and generates a symbolic expression
|
||||
// that describes it's value at the bound point. The provided variable should not contain a
|
||||
// pointer with out-of-block expressions.
|
||||
//
|
||||
symbolic::expression tracer::trace( symbolic::variable lookup )
|
||||
{
|
||||
using namespace logger;
|
||||
|
||||
// If invalid/.begin() iterator or register with "no-trace" flags, return as is.
|
||||
//
|
||||
if ( lookup.at.is_begin() || ( lookup.is_register() && ( lookup.reg().flags & ( register_volatile | register_readonly ) ) ) )
|
||||
return lookup.to_expression();
|
||||
|
||||
#ifdef _DEBUG
|
||||
// If memory, make sure pointer is expressed as a variable within current block.
|
||||
//
|
||||
if ( lookup.is_memory() )
|
||||
{
|
||||
using validator_t = std::function<void( const symbolic::expression& )>;
|
||||
static const std::function<validator_t( const basic_block* )> make_validator = [ ] ( const basic_block* container )
|
||||
{
|
||||
return[ container = std::move( container ) ]( auto& exp )
|
||||
{
|
||||
if ( exp.is_variable() )
|
||||
{
|
||||
// Make sure it either has no iterator or belongs to the current container.
|
||||
//
|
||||
auto& var = exp.uid.get<symbolic::variable>();
|
||||
fassert( !var.at.is_valid() || var.at.container == container );
|
||||
|
||||
// If memory variable, validate pointer as well.
|
||||
//
|
||||
if ( var.is_memory() )
|
||||
var.mem().decay().enumerate( make_validator( container ) );
|
||||
}
|
||||
};
|
||||
};
|
||||
lookup.mem().decay().enumerate( make_validator( lookup.at.container ) );
|
||||
}
|
||||
#endif
|
||||
|
||||
// Fast forward until iterator writes to the lookup, if none found return as is.
|
||||
//
|
||||
access_details details = {};
|
||||
while ( true )
|
||||
{
|
||||
// If we reached the beginning, return as is.
|
||||
//
|
||||
if ( lookup.at.is_begin() )
|
||||
return lookup.to_expression();
|
||||
|
||||
// Decrement iterator.
|
||||
//
|
||||
--lookup.at;
|
||||
|
||||
// If variable is being written to, break.
|
||||
//
|
||||
if ( details = test_access( lookup.at, lookup.descriptor, this, access_type::write ) )
|
||||
break;
|
||||
}
|
||||
|
||||
// If fails due to offset/size mismatch, invoke partial tracer.
|
||||
//
|
||||
bitcnt_t result_bcnt = lookup.bit_count();
|
||||
if ( !details.is_unknown() &&
|
||||
( details.bit_offset != 0 || details.bit_count != result_bcnt ) )
|
||||
{
|
||||
// Define partial tracer.
|
||||
//
|
||||
partial_tracer_t ptrace;
|
||||
if ( lookup.is_register() )
|
||||
{
|
||||
ptrace = [ &, ® = lookup.reg(),
|
||||
it = std::next( lookup.at ) ]( bitcnt_t bit_offset, bitcnt_t bit_count )
|
||||
{
|
||||
symbolic::variable::register_t tmp = {
|
||||
reg.flags,
|
||||
reg.local_id,
|
||||
bit_count,
|
||||
reg.bit_offset + bit_offset
|
||||
};
|
||||
return trace( { it, tmp } );
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
ptrace = [ &, &mem = lookup.mem(),
|
||||
it = std::next( lookup.at ) ]( bitcnt_t bit_offset, bitcnt_t bit_count )
|
||||
{
|
||||
fassert( !( ( bit_offset | bit_count ) & 7 ) );
|
||||
symbolic::variable::memory_t tmp = {
|
||||
mem.decay() + bit_offset / 8,
|
||||
bit_count
|
||||
};
|
||||
return trace( { it, tmp } );
|
||||
};
|
||||
}
|
||||
|
||||
// Redirect to partial resolver.
|
||||
//
|
||||
return resolve_partial( details, result_bcnt, ptrace );
|
||||
}
|
||||
|
||||
// Create a lambda virtual machine and allocate a temporary result.
|
||||
//
|
||||
lambda_vm lvm;
|
||||
symbolic::expression result = {};
|
||||
|
||||
lvm.hooks.read_register = [ & ] ( const register_desc& desc )
|
||||
{
|
||||
return trace( { lookup.at, desc } );
|
||||
};
|
||||
lvm.hooks.read_memory = [ & ] ( const symbolic::expression& pointer, size_t byte_count )
|
||||
{
|
||||
auto exp = trace( symbolic::variable{
|
||||
lookup.at,{ pointer, bitcnt_t( byte_count * 8 ) }
|
||||
} );
|
||||
return exp.is_valid() ? exp.resize( result_bcnt ) : exp;
|
||||
};
|
||||
lvm.hooks.write_register = [ & ] ( const register_desc& desc, symbolic::expression value )
|
||||
{
|
||||
if ( desc == lookup.reg() )
|
||||
result = std::move( value );
|
||||
};
|
||||
|
||||
lvm.hooks.write_memory = [ & ] ( const symbolic::expression& pointer, symbolic::expression value )
|
||||
{
|
||||
if ( pointer.equals( lookup.mem().decay() ) )
|
||||
result = std::move( value );
|
||||
};
|
||||
|
||||
// If access details are known:
|
||||
//
|
||||
if ( !details.is_unknown() )
|
||||
{
|
||||
// Step one instruction, if result was successfuly captured, return.
|
||||
//
|
||||
if ( lvm.execute( *lookup.at ), result )
|
||||
return result;
|
||||
}
|
||||
// If they are unknown, fallthrough to fail.
|
||||
//
|
||||
else
|
||||
{
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the state.
|
||||
//
|
||||
log<CON_RED>( "[Unknown symbolic state.]\n" );
|
||||
#endif
|
||||
}
|
||||
|
||||
// If we could not describe the behaviour, increment iterator and return.
|
||||
//
|
||||
++lookup.at;
|
||||
return lookup.to_expression();
|
||||
}
|
||||
|
||||
// Traces a variable across the entire routine and tries to generates a symbolic expression
|
||||
// for it at the specified point of the block.
|
||||
//
|
||||
symbolic::expression tracer::rtrace( symbolic::variable lookup )
|
||||
{
|
||||
return rtrace_primitive( lookup, this, {} );
|
||||
}
|
||||
};
|
||||
30
VTIL-Architecture/trace/tracer.hpp
Normal file
30
VTIL-Architecture/trace/tracer.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
#include <vtil/symex>
|
||||
#include "../symex/variable.hpp"
|
||||
|
||||
namespace vtil
|
||||
{
|
||||
struct tracer
|
||||
{
|
||||
// Traces a variable across the basic block it belongs to and generates a symbolic expression
|
||||
// that describes it's value at the bound point. The provided variable should not contain a
|
||||
// pointer with out-of-block expressions.
|
||||
//
|
||||
virtual symbolic::expression trace( symbolic::variable lookup );
|
||||
|
||||
// Traces a variable across the entire routine and tries to generates a symbolic expression
|
||||
// for it at the specified point of the block.
|
||||
//
|
||||
virtual symbolic::expression rtrace( symbolic::variable lookup );
|
||||
|
||||
// Wrappers around the functions above that return expressions with the registers packed.
|
||||
//
|
||||
symbolic::expression trace_p( symbolic::variable lookup ) { return symbolic::variable::pack_all( trace( std::move( lookup ) ) ); }
|
||||
symbolic::expression rtrace_p( symbolic::variable lookup ) { return symbolic::variable::pack_all( rtrace( std::move( lookup ) ) ); }
|
||||
|
||||
// Operator() wraps trace_p and [] wraps rtrace_p.
|
||||
//
|
||||
auto operator()( symbolic::variable lookup ) { return trace_p( std::move( lookup ) ); }
|
||||
auto operator[]( symbolic::variable lookup ) { return rtrace_p( std::move( lookup ) ); }
|
||||
};
|
||||
};
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#include "cached_tracer.hpp"
|
||||
|
||||
namespace vtil::optimizer
|
||||
{
|
||||
// Replicate trace_basic with the addition of a cache lookup.
|
||||
//
|
||||
symbolic::expression cached_tracer::trace_basic_cached( const symbolic::variable& lookup, const trace_function_t& tracer )
|
||||
{
|
||||
using namespace logger;
|
||||
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the beginning of the trace.
|
||||
//
|
||||
log<CON_BRG>( "CcTrace(%s)\n", lookup );
|
||||
scope_padding _p( 1 );
|
||||
#endif
|
||||
// Handle base case.
|
||||
//
|
||||
if ( lookup.at.is_begin() )
|
||||
{
|
||||
symbolic::expression result = lookup.to_expression();
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log result.
|
||||
//
|
||||
log<CON_BRG>( "= %s [Base case]\n", result );
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
// Try lookup the exact variable in the map in a fast manner.
|
||||
//
|
||||
auto it = cache.find( lookup );
|
||||
if ( it != cache.end() )
|
||||
{
|
||||
const symbolic::expression& result = *it->second;
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log result.
|
||||
//
|
||||
log<CON_BLU>( "= %s [Cached result]\n", result );
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
// Declare a predicate for the search of the variable in the cache.
|
||||
//
|
||||
std::function<bool( const cache_entry& )> predicate;
|
||||
|
||||
// If memory variable:
|
||||
//
|
||||
if ( lookup.is_memory() )
|
||||
{
|
||||
predicate = [ & ] ( const cache_entry& pair )
|
||||
{
|
||||
// Key must be of memory type at the same position.
|
||||
//
|
||||
if ( !pair.first.is_memory() ) return false;
|
||||
if ( pair.first.at != lookup.at ) return false;
|
||||
|
||||
// Must be the same pointer and have a larger or equal size.
|
||||
//
|
||||
auto& self = lookup.mem();
|
||||
auto& other = pair.first.mem();
|
||||
return self.decay().equals( other.decay() ) &&
|
||||
self.bit_count >= other.bit_count;
|
||||
};
|
||||
}
|
||||
// If register variable:
|
||||
//
|
||||
else
|
||||
{
|
||||
fassert( lookup.is_register() );
|
||||
predicate = [ & ] ( const cache_entry& pair )
|
||||
{
|
||||
// Key must be of memory type at the same position.
|
||||
//
|
||||
if ( !pair.first.is_register() ) return false;
|
||||
if ( pair.first.at != lookup.at ) return false;
|
||||
|
||||
// Must be the same register and have a larger or equal size.
|
||||
//
|
||||
auto& self = lookup.reg();
|
||||
auto& other = pair.first.reg();
|
||||
return self.flags == other.flags &&
|
||||
self.local_id == other.local_id &&
|
||||
self.bit_offset == other.bit_offset &&
|
||||
self.bit_count >= other.bit_count;
|
||||
};
|
||||
}
|
||||
|
||||
// Search the map, if we find a matching entry shrink and use as the result.
|
||||
//
|
||||
symbolic::expression result;
|
||||
it = std::find_if( cache.begin(), cache.end(), predicate );
|
||||
if ( it != cache.end() )
|
||||
result = symbolic::expression{ *it->second }.resize( lookup.bit_count() );
|
||||
else
|
||||
result = trace_primitive( lookup, tracer ? tracer : *this );
|
||||
|
||||
// Insert a cache entry for the exact variable we're looking up and return.
|
||||
//
|
||||
cache.emplace( lookup, result );
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log result.
|
||||
//
|
||||
log<CON_BRG>( "= %s\n", result );
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
// Wrappers of trace and rtrace with cached basic tracer.
|
||||
//
|
||||
symbolic::expression cached_tracer::trace( const symbolic::variable& lookup, bool pack )
|
||||
{
|
||||
symbolic::expression&& result = trace_basic_cached( lookup );
|
||||
return pack ? symbolic::variable::pack_all( result ) : result;
|
||||
}
|
||||
symbolic::expression cached_tracer::rtrace( const symbolic::variable& lookup, bool pack )
|
||||
{
|
||||
symbolic::expression&& result = rtrace_primitive( lookup, *this );
|
||||
return pack ? symbolic::variable::pack_all( result ) : result;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,447 +0,0 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#include <vtil/vm>
|
||||
#include "trace.hpp"
|
||||
#include "variable_aux.hpp"
|
||||
|
||||
namespace vtil::optimizer
|
||||
{
|
||||
// Basic tracer with the trace_function_t signature implemented using primitive tracer.
|
||||
//
|
||||
static symbolic::expression trace_basic( const symbolic::variable& lookup )
|
||||
{
|
||||
using namespace logger;
|
||||
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the beginning of the trace.
|
||||
//
|
||||
log<CON_BRG>( "Trace(%s)\n", lookup );
|
||||
scope_padding _p( 1 );
|
||||
#endif
|
||||
|
||||
// If base case reached, convert to an expression and return as is,
|
||||
// otherwise invoke primitive tracer.
|
||||
//
|
||||
symbolic::expression result = lookup.at.is_begin()
|
||||
? lookup.to_expression()
|
||||
: trace_primitive( lookup, trace_basic );
|
||||
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log result.
|
||||
//
|
||||
log<CON_BRG>( "= %s\n", result );
|
||||
#endif
|
||||
return result;
|
||||
};
|
||||
|
||||
// Propagates all variables in the reference expression onto the new iterator given using
|
||||
// the query helper given (except of pointer propagation where basic tracer will be used).
|
||||
// - Note: New iterator should be a connected block's end.
|
||||
//
|
||||
static symbolic::expression propagate( const symbolic::expression& ref,
|
||||
const il_const_iterator& it,
|
||||
const trace_function_t& tracer )
|
||||
{
|
||||
using namespace logger;
|
||||
scope_padding _p( 3 );
|
||||
|
||||
// Copy the reference expression.
|
||||
//
|
||||
symbolic::expression exp = ref;
|
||||
|
||||
// For each unique variable:
|
||||
//
|
||||
std::set<symbolic::unique_identifier> variables;
|
||||
ref.count_unique_variables( &variables );
|
||||
for ( auto& uid : variables )
|
||||
{
|
||||
// Move the variable to reference the previous block.
|
||||
//
|
||||
symbolic::variable var = uid.get<symbolic::variable>();
|
||||
|
||||
// Skip if variable is position indepdendent or not at the beginning of the block.
|
||||
//
|
||||
if ( !var.at.is_valid() || !var.at.is_begin() )
|
||||
continue;
|
||||
|
||||
// If register:
|
||||
//
|
||||
if ( var.is_register() )
|
||||
{
|
||||
// Local temporary must not exist in an expression being propagated
|
||||
// from the beginning of the block as that indicates use before assignment.
|
||||
// Make sure this is not the case.
|
||||
//
|
||||
if ( var.reg().flags & register_local )
|
||||
error( "Local variable %s is used before value assignment.\n", var );
|
||||
|
||||
// If volatile iterator cannot be moved, skip.
|
||||
//
|
||||
if ( var.reg().flags & register_volatile )
|
||||
continue;
|
||||
}
|
||||
// If memory, propagate the pointer.
|
||||
//
|
||||
else if ( var.is_memory() )
|
||||
{
|
||||
auto& pointer = var.mem().decay();
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log original pointer.
|
||||
//
|
||||
log<CON_PRP>( "Propagating pointer: %s\n", pointer->to_string() );
|
||||
#endif
|
||||
// Fail if propagation fails.
|
||||
//
|
||||
if ( !( pointer = propagate( pointer, it, trace_basic ) ) )
|
||||
return {};
|
||||
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log new pointer.
|
||||
//
|
||||
log<CON_PRP>( "Pointer' => %s\n", pointer->to_string() );
|
||||
#endif
|
||||
}
|
||||
|
||||
// Move the assigned iterator.
|
||||
//
|
||||
var.at = it;
|
||||
|
||||
// Trace the variable in the destination block, fail if it fails.
|
||||
//
|
||||
symbolic::expression var_traced = tracer( var );
|
||||
if ( !var_traced )
|
||||
return {};
|
||||
|
||||
// If we are tracing the value of RSP, add the stack pointer delta between blocks.
|
||||
//
|
||||
if ( var.is_register() && var.reg().is_stack_pointer() )
|
||||
var_traced = var_traced + it.container->sp_offset;
|
||||
|
||||
// Rewrite the expression
|
||||
//
|
||||
exp.transform( [ & ] ( symbolic::expression& exp )
|
||||
{
|
||||
if ( exp.is_variable() && exp.uid == uid )
|
||||
exp = var_traced;
|
||||
} );
|
||||
}
|
||||
|
||||
// Return the result.
|
||||
//
|
||||
return exp;
|
||||
}
|
||||
|
||||
// Traces a variable across the basic block it belongs to and generates a symbolic expression
|
||||
// that describes it's value at the bound point. Will invoke the passed tracer for any additional
|
||||
// tracing it requires.
|
||||
//
|
||||
symbolic::expression trace_primitive( symbolic::variable lookup, const trace_function_t& tracer )
|
||||
{
|
||||
using namespace logger;
|
||||
|
||||
// If null iterator or register with "no-trace" flags, return as is.
|
||||
//
|
||||
if ( !lookup.at.is_valid() ||
|
||||
( lookup.is_register() && ( lookup.reg().flags & ( register_volatile | register_readonly ) ) ) )
|
||||
return lookup.to_expression();
|
||||
|
||||
#ifdef _DEBUG
|
||||
// If memory, make sure pointer is expressed as a variable within current block.
|
||||
//
|
||||
if ( lookup.is_memory() )
|
||||
{
|
||||
using validator_t = std::function<void( const symbolic::expression& )>;
|
||||
static const std::function<validator_t( const basic_block* )> make_validator = [ ] ( const basic_block* container )
|
||||
{
|
||||
return[ container = std::move( container ) ]( auto& exp )
|
||||
{
|
||||
if ( exp.is_variable() )
|
||||
{
|
||||
// Make sure it either has no iterator or belongs to the current container.
|
||||
//
|
||||
auto& var = exp.uid.get<symbolic::variable>();
|
||||
fassert( !var.at.is_valid() || var.at.container == container );
|
||||
|
||||
// If memory variable, validate pointer as well.
|
||||
//
|
||||
if ( var.is_memory() )
|
||||
var.mem().decay().enumerate( make_validator( container ) );
|
||||
}
|
||||
};
|
||||
};
|
||||
lookup.mem().decay().enumerate( make_validator( lookup.at.container ) );
|
||||
}
|
||||
#endif
|
||||
// Fast forward until iterator writes to the lookup, if none found return as is.
|
||||
//
|
||||
access_details details = {};
|
||||
while ( true )
|
||||
{
|
||||
// If we reached the beginning without any modifications, redirect to the helper passed.
|
||||
//
|
||||
if ( lookup.at.is_begin() )
|
||||
return tracer( lookup );
|
||||
|
||||
// Decrement iterator.
|
||||
//
|
||||
--lookup.at;
|
||||
|
||||
// If variable is being written to, break.
|
||||
//
|
||||
if ( details = test_access( lookup.at, lookup.descriptor, access_type::write, tracer ) )
|
||||
break;
|
||||
}
|
||||
|
||||
// If fails due to offset/size mismatch, invoke partial tracer.
|
||||
//
|
||||
bitcnt_t result_bcnt = lookup.bit_count();
|
||||
if ( !details.is_unknown() &&
|
||||
( details.bit_offset != 0 || details.bit_count != result_bcnt ) )
|
||||
{
|
||||
// Define partial tracer.
|
||||
//
|
||||
partial_tracer_t ptrace;
|
||||
if ( lookup.is_register() )
|
||||
{
|
||||
ptrace = [ &, ® = lookup.reg(),
|
||||
it = std::next( lookup.at ) ] ( bitcnt_t bit_offset, bitcnt_t bit_count )
|
||||
{
|
||||
symbolic::variable::register_t tmp = {
|
||||
reg.flags,
|
||||
reg.local_id,
|
||||
bit_count,
|
||||
reg.bit_offset + bit_offset
|
||||
};
|
||||
return tracer( { it, tmp } );
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
ptrace = [ &, &mem = lookup.mem(),
|
||||
it = std::next( lookup.at ) ] ( bitcnt_t bit_offset, bitcnt_t bit_count )
|
||||
{
|
||||
fassert( !( ( bit_offset | bit_count ) & 7 ) );
|
||||
symbolic::variable::memory_t tmp = {
|
||||
mem.decay() + bit_offset / 8,
|
||||
bit_count
|
||||
};
|
||||
return tracer( { it, tmp } );
|
||||
};
|
||||
}
|
||||
|
||||
// Redirect to partial resolver.
|
||||
//
|
||||
return resolve_partial( details, result_bcnt, ptrace );
|
||||
}
|
||||
|
||||
// Create a lambda virtual machine and allocate a temporary result.
|
||||
//
|
||||
lambda_vm lvm;
|
||||
symbolic::expression result = {};
|
||||
|
||||
lvm.hooks.read_register = [ & ] ( const register_desc& desc )
|
||||
{
|
||||
return tracer( { lookup.at, desc } );
|
||||
};
|
||||
lvm.hooks.read_memory = [ & ] ( const symbolic::expression& pointer, size_t byte_count )
|
||||
{
|
||||
auto exp = tracer( symbolic::variable{
|
||||
lookup.at, { pointer, bitcnt_t( byte_count * 8 ) }
|
||||
} );
|
||||
return exp.is_valid() ? exp.resize( result_bcnt ) : exp;
|
||||
};
|
||||
lvm.hooks.write_register = [ & ] ( const register_desc& desc, symbolic::expression value )
|
||||
{
|
||||
if ( desc == lookup.reg() )
|
||||
result = std::move( value );
|
||||
};
|
||||
|
||||
lvm.hooks.write_memory = [ & ] ( const symbolic::expression& pointer, symbolic::expression value )
|
||||
{
|
||||
if ( pointer.equals( lookup.mem().decay() ) )
|
||||
result = std::move( value );
|
||||
};
|
||||
|
||||
// If access details are known:
|
||||
//
|
||||
if ( !details.is_unknown() )
|
||||
{
|
||||
// Step one instruction, if result was successfuly captured, return.
|
||||
//
|
||||
if ( lvm.execute( *lookup.at ), result )
|
||||
return result;
|
||||
}
|
||||
// If they are unknown, fallthrough to fail.
|
||||
//
|
||||
else
|
||||
{
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the state.
|
||||
//
|
||||
log<CON_RED>( "[Unknown symbolic state.]\n" );
|
||||
#endif
|
||||
}
|
||||
|
||||
// If we could not describe the behaviour, increment iterator and return.
|
||||
//
|
||||
++lookup.at;
|
||||
return lookup.to_expression();
|
||||
}
|
||||
|
||||
// Traces a variable across the entire routine and generates a symbolic expression that describes
|
||||
// it's value at the bound point. Will invoke the passed tracer for any additional tracing it requires.
|
||||
// Takes an optional path history used internally to recurse in a controlled fashion.
|
||||
//
|
||||
symbolic::expression rtrace_primitive( const symbolic::variable& lookup, const trace_function_t& tracer, const path_history_t& history )
|
||||
{
|
||||
using namespace logger;
|
||||
|
||||
// Trace through the current block first.
|
||||
//
|
||||
symbolic::expression result = tracer( lookup );
|
||||
|
||||
// If result has any variables:
|
||||
//
|
||||
if ( result.count_unique_variables() != 0 )
|
||||
{
|
||||
// Determine the paths we can take to iterate further.
|
||||
//
|
||||
std::vector it_list = lookup.at.is_valid()
|
||||
? lookup.at.recurse( false )
|
||||
: std::vector<il_const_iterator>{};
|
||||
|
||||
// If there are paths take.
|
||||
//
|
||||
if ( !it_list.empty() )
|
||||
{
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log recursive tracing of the expression.
|
||||
//
|
||||
log<CON_GRN>( "Base case: %s\n", result );
|
||||
#endif
|
||||
// Save current result as default result and clear it.
|
||||
//
|
||||
symbolic::expression default_result = {};
|
||||
std::swap( result, default_result );
|
||||
|
||||
// For each path:
|
||||
//
|
||||
for ( auto& it : it_list )
|
||||
{
|
||||
// Create a local copy for the visited list for this path
|
||||
// and increment the visit counter.
|
||||
//
|
||||
path_history_t history_local = history;
|
||||
uint32_t& visit_counter = history_local[ { lookup.at.container, it.container } ];
|
||||
|
||||
// If we've taken this path more than twice, skip it.
|
||||
//
|
||||
if ( ++visit_counter > 2 )
|
||||
{
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log skipping of path.
|
||||
//
|
||||
log<CON_CYN>( "Path [%llx->%llx] is not taken as it's n-looping.\n", lookup.at.container->entry_vip, it.container->entry_vip );
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log tracing of path.
|
||||
//
|
||||
log<CON_YLW>( "Taking path [%llx->%llx]\n", lookup.at.container->entry_vip, it.container->entry_vip );
|
||||
#endif
|
||||
// Propagate each variable onto to the destination block.
|
||||
//
|
||||
symbolic::expression exp = propagate( default_result, it, [ & ] ( auto& var )
|
||||
{
|
||||
return rtrace_primitive( var, tracer, history_local );
|
||||
} );
|
||||
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log result.
|
||||
//
|
||||
log<CON_BLU>( "= %s\n", exp );
|
||||
#endif
|
||||
// If no result is set yet, assign the current expression.
|
||||
//
|
||||
if ( !result.is_valid() )
|
||||
result = exp;
|
||||
|
||||
// If expression is invalid or not equal to previous result, fail.
|
||||
//
|
||||
if ( !exp.is_valid() || !exp.equals( result ) )
|
||||
{
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log decision.
|
||||
//
|
||||
log<CON_RED>( "Halting tracer as it was not deterministic.\n" );
|
||||
#endif
|
||||
// If result was null, return lookup.
|
||||
//
|
||||
if ( !exp.is_valid() )
|
||||
{
|
||||
result = lookup.to_expression();
|
||||
}
|
||||
// If it was mismatchign, return default result as branch dependant.
|
||||
//
|
||||
else
|
||||
{
|
||||
result = default_result;
|
||||
result.transform( [ ] ( symbolic::expression& exp )
|
||||
{
|
||||
if ( exp.is_variable() )
|
||||
exp.uid.get<symbolic::variable>().is_branch_dependant = true;
|
||||
}, false );
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log result.
|
||||
//
|
||||
log<CON_BRG>( "= %s\n", result );
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
// Simple wrappers around primitive trace and rtrace to return in packed format.
|
||||
//
|
||||
symbolic::expression trace( const symbolic::variable& lookup, bool pack )
|
||||
{
|
||||
symbolic::expression&& result = trace_basic( lookup );
|
||||
return pack ? symbolic::variable::pack_all( result ) : result;
|
||||
}
|
||||
symbolic::expression rtrace( const symbolic::variable& lookup, bool pack )
|
||||
{
|
||||
symbolic::expression&& result = rtrace_primitive( lookup, trace_basic );
|
||||
return pack ? symbolic::variable::pack_all( result ) : result;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,270 +0,0 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#include "variable_aux.hpp"
|
||||
|
||||
namespace vtil::optimizer
|
||||
{
|
||||
// Makes a memory variable from the given instruction's src/dst, uses the tracer
|
||||
// passed to resolve the absolute pointer.
|
||||
//
|
||||
symbolic::variable reference_memory( const il_const_iterator& it, const trace_function_t& tracer )
|
||||
{
|
||||
fassert( it->base->accesses_memory() );
|
||||
|
||||
// Generate an expression for the pointer.
|
||||
//
|
||||
auto [base, offset] = it->get_mem_loc();
|
||||
symbolic::expression ptr = tracer( { it, base } ) + offset;
|
||||
|
||||
// Create the variable type.
|
||||
//
|
||||
return {
|
||||
it,
|
||||
{ ptr, bitcnt_t( it->access_size() * 8 ) },
|
||||
};
|
||||
}
|
||||
|
||||
// Checks if the instruction given accesses the variable, optionally filtering to the
|
||||
// access type specified, tracer passed will be used to generate pointers when needed.
|
||||
//
|
||||
access_details test_access( const il_const_iterator& it, const symbolic::variable::descriptor_t& var, access_type type, const trace_function_t& tracer )
|
||||
{
|
||||
// If variable is of register type:
|
||||
//
|
||||
if ( auto reg = std::get_if<symbolic::variable::register_t>( &var ) )
|
||||
{
|
||||
// Iterate each operand:
|
||||
//
|
||||
for ( int i = 0; i < it->base->operand_count(); i++ )
|
||||
{
|
||||
// Skip if not register.
|
||||
//
|
||||
if ( !it->operands[ i ].is_register() )
|
||||
continue;
|
||||
|
||||
// Skip if access type does not match.
|
||||
//
|
||||
switch ( type )
|
||||
{
|
||||
// ::read will filter to read or read/write.
|
||||
//
|
||||
case access_type::read:
|
||||
if ( it->base->operand_types[ i ] == operand_type::write )
|
||||
continue;
|
||||
break;
|
||||
// ::write will filter to write or read/write.
|
||||
//
|
||||
case access_type::write:
|
||||
if ( it->base->operand_types[ i ] < operand_type::write )
|
||||
continue;
|
||||
break;
|
||||
// ::readwrite will filter to only read/write.
|
||||
//
|
||||
case access_type::readwrite:
|
||||
if ( it->base->operand_types[ i ] != operand_type::readwrite )
|
||||
continue;
|
||||
break;
|
||||
// ::none accepts any access.
|
||||
//
|
||||
case access_type::none:
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip if no overlap.
|
||||
//
|
||||
auto& ref_reg = it->operands[ i ].reg();
|
||||
if ( !ref_reg.overlaps( *reg ) )
|
||||
continue;
|
||||
|
||||
// Return access details.
|
||||
//
|
||||
access_type type_found;
|
||||
if ( it->base->operand_types[ i ] == operand_type::readwrite )
|
||||
type_found = access_type::readwrite;
|
||||
else if ( it->base->operand_types[ i ] == operand_type::write )
|
||||
type_found = access_type::write;
|
||||
else
|
||||
type_found = access_type::read;
|
||||
|
||||
return {
|
||||
type_found,
|
||||
ref_reg.bit_offset - reg->bit_offset,
|
||||
ref_reg.bit_count
|
||||
};
|
||||
}
|
||||
}
|
||||
// If variable is of memory type:
|
||||
//
|
||||
else if( auto mem = std::get_if<symbolic::variable::memory_t>( &var ) )
|
||||
{
|
||||
// If instruction accesses memory:
|
||||
//
|
||||
if ( it->base->accesses_memory() )
|
||||
{
|
||||
// Skip if access type does not match.
|
||||
//
|
||||
switch ( type )
|
||||
{
|
||||
// ::read will filter to read.
|
||||
//
|
||||
case access_type::read:
|
||||
if ( it->base->writes_memory() )
|
||||
return { access_type::none };
|
||||
break;
|
||||
// ::write will filter to write.
|
||||
//
|
||||
case access_type::write:
|
||||
if ( !it->base->writes_memory() )
|
||||
return { access_type::none };
|
||||
break;
|
||||
// Read/write does not exist for memory operations.
|
||||
//
|
||||
case access_type::readwrite:
|
||||
unreachable();
|
||||
// ::none accepts any access.
|
||||
//
|
||||
case access_type::none:
|
||||
// Determine the type and set it.
|
||||
//
|
||||
type = it->base->writes_memory() ? access_type::write : access_type::read;
|
||||
break;
|
||||
}
|
||||
|
||||
// Generate a pointer.
|
||||
//
|
||||
auto ref_mem = reference_memory( it, tracer ).mem();
|
||||
|
||||
// If the two pointers can overlap (not restrict qualified against each other):
|
||||
//
|
||||
if ( ref_mem.base.can_overlap( mem->base ) )
|
||||
{
|
||||
// If it can be expressed as a constant:
|
||||
//
|
||||
if ( auto disp = ( ref_mem.base - mem->base ) )
|
||||
{
|
||||
// Check if within boundaries:
|
||||
//
|
||||
int64_t low_offset = *disp;
|
||||
int64_t high_offset = low_offset + it->access_size();
|
||||
if ( low_offset < ( mem->bit_count / 8 ) && high_offset > 0 )
|
||||
{
|
||||
// Can safely multiply by 8 and shrink to bitcnt_t type from int64_t
|
||||
// since variables are of maximum 64-bit size which means both offset
|
||||
// and size will be small numbers.
|
||||
//
|
||||
return {
|
||||
type,
|
||||
bitcnt_t( low_offset * 8 ),
|
||||
bitcnt_t( ( high_offset - low_offset ) * 8 )
|
||||
};
|
||||
}
|
||||
}
|
||||
// Otherwise, return unknown.
|
||||
//
|
||||
else
|
||||
{
|
||||
return { type, 0, -1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No access case.
|
||||
//
|
||||
return { access_type::none };
|
||||
}
|
||||
|
||||
// Given a partial tracer, this routine will determine the full value of the variable
|
||||
// at the given position where a partial write was found.
|
||||
//
|
||||
symbolic::expression resolve_partial( const access_details& access, bitcnt_t bit_count, const partial_tracer_t& ptracer )
|
||||
{
|
||||
using namespace logger;
|
||||
|
||||
// Fetch the result of this operation.
|
||||
//
|
||||
symbolic::expression base = ptracer( access.bit_offset, access.bit_count );
|
||||
|
||||
// Trace a low part if we have to.
|
||||
//
|
||||
if ( access.bit_offset > 0 )
|
||||
{
|
||||
bitcnt_t low_bcnt = access.bit_offset;
|
||||
auto res = ptracer( 0, low_bcnt );
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the low and middle bits.
|
||||
//
|
||||
log<CON_RED>( "dst[00..%02d] := %s\n", low_bcnt, res );
|
||||
log<CON_YLW>( "dst[%02d..%02d] := %s\n", access.bit_offset, access.bit_offset + access.bit_count, base );
|
||||
#endif
|
||||
base = res | ( base.resize( bit_count ) << low_bcnt );
|
||||
}
|
||||
// Shift the result if we have to.
|
||||
//
|
||||
else if ( access.bit_offset < 0 )
|
||||
{
|
||||
base = ( base >> access.bit_offset ).resize( bit_count );
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the low bits after shifting.
|
||||
//
|
||||
log<CON_YLW>( "dst[00..%02d] := %s\n", access.bit_offset + access.bit_count, base );
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the low bits.
|
||||
//
|
||||
log<CON_YLW>( "dst[00..%02d] := %s\n", access.bit_offset + access.bit_count, base );
|
||||
#endif
|
||||
}
|
||||
|
||||
// Trace a high part if we have to.
|
||||
//
|
||||
if ( bit_count > ( access.bit_offset + access.bit_count ) )
|
||||
{
|
||||
bitcnt_t high_bnct = bit_count - ( access.bit_offset + access.bit_count );
|
||||
auto res = ptracer( access.bit_offset + access.bit_count, high_bnct );
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the high bits.
|
||||
//
|
||||
log<CON_PRP>( "dst[%02d..%02d] := %s\n", access.bit_offset + access.bit_count, bit_count, res );
|
||||
#endif
|
||||
base = base | ( res.resize( bit_count ) << ( access.bit_offset + access.bit_count ) );
|
||||
}
|
||||
|
||||
#if VTIL_OPT_TRACE_VERBOSE
|
||||
// Log the final result.
|
||||
//
|
||||
log<CON_GRN>( "dst := %s\n", base );
|
||||
#endif
|
||||
// Resize and return.
|
||||
//
|
||||
return base.resize( bit_count );
|
||||
}
|
||||
};
|
||||
|
|
@ -29,8 +29,6 @@
|
|||
#include <vector>
|
||||
#include <vtil/query>
|
||||
#include <vtil/symex>
|
||||
#include "../analysis/cached_tracer.hpp"
|
||||
#include "../analysis/variable_aux.hpp"
|
||||
|
||||
namespace vtil::optimizer
|
||||
{
|
||||
|
|
@ -60,8 +58,8 @@ namespace vtil::optimizer
|
|||
// Calculate the difference between current virtual stack pointer
|
||||
// and the next stack pointer instance.
|
||||
//
|
||||
auto sp_curr = ctrace.trace( { it, REG_SP } ) + it->sp_offset;
|
||||
auto sp_next = ctrace.trace( { std::next( it ), REG_SP } );
|
||||
auto sp_curr = ctrace( { it, REG_SP } ) + it->sp_offset;
|
||||
auto sp_next = ctrace( { std::next( it ), REG_SP } );
|
||||
|
||||
// If it simplifies to a constant, replace with a stack shift.
|
||||
//
|
||||
|
|
@ -102,8 +100,8 @@ namespace vtil::optimizer
|
|||
{
|
||||
// Try to simplify pointer to SP + C.
|
||||
//
|
||||
auto delta = ctrace.trace( { it, it->get_mem_loc().first } ) -
|
||||
ctrace.trace( { it, REG_SP } );
|
||||
auto delta = ctrace( { it, it->get_mem_loc().first } ) -
|
||||
ctrace( { it, REG_SP } );
|
||||
|
||||
// If successful, replace the operands.
|
||||
//
|
||||
|
|
@ -125,34 +123,36 @@ namespace vtil::optimizer
|
|||
// Wrap cached tracer with a filter that returns a constant pseudo-variable for each
|
||||
// register query representing $sp and rejects queries of registers.
|
||||
//
|
||||
cached_tracer ctrace = {};
|
||||
trace_function_t lazy_tracer = [ & ] ( const symbolic::variable& lookup )
|
||||
struct lazy_tracer : cached_tracer
|
||||
{
|
||||
// If register:
|
||||
//
|
||||
if ( lookup.is_register() )
|
||||
symbolic::expression trace( symbolic::variable lookup ) override
|
||||
{
|
||||
// If stack pointer, return unique pseudo-register per stack instance.
|
||||
// If register:
|
||||
//
|
||||
if ( lookup.reg().is_stack_pointer() )
|
||||
if ( lookup.is_register() )
|
||||
{
|
||||
register_desc desc = {
|
||||
register_local,
|
||||
lookup.at->sp_index,
|
||||
lookup.reg().bit_count
|
||||
};
|
||||
return symbolic::variable{ lookup.at.container->begin(), desc }.to_expression();
|
||||
// If stack pointer, return unique pseudo-register per stack instance.
|
||||
//
|
||||
if ( lookup.reg().is_stack_pointer() )
|
||||
{
|
||||
register_desc desc = {
|
||||
register_local,
|
||||
lookup.at->sp_index,
|
||||
lookup.reg().bit_count
|
||||
};
|
||||
return symbolic::variable{ lookup.at.container->begin(), desc }.to_expression();
|
||||
}
|
||||
|
||||
// Otherwise, return without tracing.
|
||||
//
|
||||
return lookup.to_expression();
|
||||
}
|
||||
|
||||
// Otherwise, return without tracing.
|
||||
// Fallback to default tracer.
|
||||
//
|
||||
return lookup.to_expression();
|
||||
return cached_tracer::trace( lookup );
|
||||
}
|
||||
|
||||
// Fallback to default tracer.
|
||||
//
|
||||
return ctrace.trace_basic_cached( lookup, lazy_tracer );
|
||||
};
|
||||
} tracer = {};
|
||||
|
||||
// => Begin a foward iterating query.
|
||||
//
|
||||
|
|
@ -174,7 +174,8 @@ namespace vtil::optimizer
|
|||
|
||||
// Lazy-trace the value.
|
||||
//
|
||||
symbolic::expression exp = trace_primitive( reference_memory( it, lazy_tracer ), lazy_tracer );
|
||||
symbolic::pointer ptr = { tracer( { it, REG_SP } ) + it->get_mem_loc().second };
|
||||
symbolic::expression exp = tracer( { it, { ptr, bitcnt_t( it->access_size() * 8 ) } } );
|
||||
|
||||
// Resize and pack variables.
|
||||
//
|
||||
|
|
@ -232,7 +233,7 @@ namespace vtil::optimizer
|
|||
//
|
||||
bool is_alive = !reg.is_volatile();
|
||||
for ( auto it2 = access_point; !it2.is_end() && is_alive && it2 != it; it2++ )
|
||||
is_alive &= !test_access( it2, var.descriptor, access_type::write );
|
||||
is_alive &= !test_access( it2, var.descriptor, &tracer, access_type::write );
|
||||
|
||||
// If not, try hijacking the value declaration.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -2,5 +2,4 @@
|
|||
#include <vtil/common>
|
||||
#include <vtil/symex>
|
||||
#include <vtil/arch>
|
||||
#include <vtil/optimizer>
|
||||
#include <vtil/vm>
|
||||
#include <vtil/optimizer>
|
||||
Loading…
Add table
Add a link
Reference in a new issue