mirror of
https://github.com/vtil-project/VTIL-Core
synced 2026-08-17 08:23:03 -04:00
736 lines
No EOL
20 KiB
C++
736 lines
No EOL
20 KiB
C++
// 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.hpp"
|
|
#include "../trace/tracer.hpp"
|
|
#include "../routine/call_convention.hpp"
|
|
|
|
namespace vtil::symbolic
|
|
{
|
|
// Dummy iterator to be used when variable is not being tracked within a block.
|
|
//
|
|
static const il_const_iterator free_form_iterator = [ ] ()
|
|
{
|
|
// Create a dummy invalid block with an invalid instruction and reference it.
|
|
//
|
|
static basic_block dummy_block;
|
|
dummy_block.stream.push_back( {} );
|
|
return dummy_block.begin();
|
|
}();
|
|
|
|
// Returns the origin block of the pointer.
|
|
//
|
|
static const basic_block* get_pointer_origin( const symbolic::expression& exp )
|
|
{
|
|
// If variable with valid iterator, return the block.
|
|
//
|
|
if ( exp.is_variable() )
|
|
{
|
|
auto& var = exp.uid.get<symbolic::variable>();
|
|
if ( var.at.is_valid() )
|
|
return var.at.container;
|
|
}
|
|
|
|
// Otherwise try each child.
|
|
//
|
|
for ( auto& exp : { exp.lhs, exp.rhs } )
|
|
if ( auto p = exp ? get_pointer_origin( *exp ) : nullptr )
|
|
return p;
|
|
|
|
// Fail.
|
|
//
|
|
return nullptr;
|
|
}
|
|
|
|
// Calculates the displacement between two pointers and fills the access_details accordingly.
|
|
//
|
|
static void fill_displacement( access_details* details, const pointer& p1, const pointer& p2, tracer* tracer, bool xblock )
|
|
{
|
|
// If valid tracer provided:
|
|
//
|
|
if ( tracer )
|
|
{
|
|
// If two pointers' origins mismatch, propagate first.
|
|
//
|
|
auto o1 = get_pointer_origin( p1.base );
|
|
auto o2 = get_pointer_origin( p2.base );
|
|
if ( o1 != o2 && o1 && o2 )
|
|
{
|
|
// Allocate temporary storage for new pointers.
|
|
//
|
|
pointer pn1, pn2;
|
|
std::array in = { &p1, &p2 };
|
|
std::array out = { &pn1, &pn2 };
|
|
|
|
// For each pointer:
|
|
//
|
|
for ( auto [in, out] : zip( in, out ) )
|
|
{
|
|
// Transform base pointer:
|
|
//
|
|
symbolic::expression base = in->base;
|
|
base.transform( [ & ] ( symbolic::expression& exp )
|
|
{
|
|
// Skip if not variable.
|
|
//
|
|
if ( !exp.is_variable() )
|
|
return;
|
|
variable var = exp.uid.get<variable>();
|
|
|
|
// Skip if it has an invalid iterator.
|
|
//
|
|
if ( !var.at.is_valid() )
|
|
return;
|
|
|
|
// Determine all paths and path restrict the iterator.
|
|
//
|
|
auto& pathset_1 = o1->owner->path_cache[ 1 ][ o1 ][ var.at.container ];
|
|
auto& pathset_2 = o2->owner->path_cache[ 1 ][ o2 ][ var.at.container ];
|
|
var.at.is_path_restricted = true;
|
|
|
|
// If only one of the paths are valid for backwards iteration:
|
|
//
|
|
if ( pathset_1.empty() ^ pathset_2.empty() )
|
|
{
|
|
// Set the restriction.
|
|
//
|
|
var.at.paths_allowed = pathset_1.empty() ? &pathset_2 : &pathset_1;
|
|
exp = tracer->rtrace( std::move( var ) );
|
|
}
|
|
// If both paths are valid for backwards iteration:
|
|
//
|
|
else if( pathset_1.size() && pathset_2.size() )
|
|
{
|
|
// Calculate for both and set if equivalent.
|
|
//
|
|
var.at.paths_allowed = &pathset_1;
|
|
auto exp1 = tracer->rtrace( var );
|
|
var.at.paths_allowed = &pathset_2;
|
|
auto exp2 = tracer->rtrace( var );
|
|
if ( exp1.equals( exp2 ) )
|
|
exp = exp1;
|
|
}
|
|
} );
|
|
|
|
// Write the new pointer.
|
|
//
|
|
*out = pointer{ base };
|
|
}
|
|
|
|
// Recurse with the new pointers.
|
|
//
|
|
return fill_displacement( details, pn1, pn2, nullptr, xblock );
|
|
}
|
|
}
|
|
|
|
// If the two pointers can overlap (not restrict qualified against each other),
|
|
// write dummy bit count and the offset if we can calculate it.
|
|
//
|
|
if ( p1.can_overlap( p2 ) )
|
|
{
|
|
details->bit_count = -1;
|
|
|
|
// If offset is constant:
|
|
//
|
|
if ( auto disp = p1 - p2 )
|
|
{
|
|
details->bit_offset = math::narrow_cast< bitcnt_t >( *disp * 8 );
|
|
}
|
|
// If pointer does not strictly overlap and cross-block and tracer
|
|
// is given, try again after cross-tracing.
|
|
//
|
|
else if ( xblock && tracer && !p1.can_overlap_s( p2 ) )
|
|
{
|
|
pointer p1r = { tracer->rtrace_exp( p1.base ) };
|
|
pointer p2r = { tracer->rtrace_exp( p2.base ) };
|
|
return fill_displacement( details, p1r, p2r, nullptr, false );
|
|
}
|
|
// If all fails, declare unknown.
|
|
//
|
|
else
|
|
{
|
|
details->unknown = 1;
|
|
}
|
|
}
|
|
// Otherwise declare no-overlap.
|
|
//
|
|
else
|
|
{
|
|
details->bit_count = 0;
|
|
details->bit_offset = 0;
|
|
}
|
|
}
|
|
|
|
// Implement generic access check for ::read_by & ::written_by, write and read must not be both set to true.
|
|
//
|
|
static access_details test_access( const variable& var, const il_const_iterator& it, tracer* tracer, bool write, bool read, bool xblock )
|
|
{
|
|
fassert( !( write && read ) );
|
|
|
|
// If variable is of register type:
|
|
//
|
|
if ( auto reg = std::get_if<variable::register_t>( &var.descriptor ) )
|
|
{
|
|
// 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.
|
|
//
|
|
if ( write && it->base->operand_types[ i ] < operand_type::write )
|
|
continue;
|
|
if ( read && it->base->operand_types[ i ] == operand_type::write )
|
|
continue;
|
|
|
|
// Skip if no overlap.
|
|
//
|
|
auto& ref_reg = it->operands[ i ].reg();
|
|
if ( !ref_reg.overlaps( *reg ) )
|
|
continue;
|
|
|
|
// Return access details.
|
|
//
|
|
return {
|
|
.bit_offset = ref_reg.bit_offset - reg->bit_offset,
|
|
.bit_count = ref_reg.bit_count,
|
|
.read = it->base->operand_types[ i ] != operand_type::write,
|
|
.write = it->base->operand_types[ i ] >= operand_type::write
|
|
};
|
|
}
|
|
}
|
|
// If variable is of memory type:
|
|
//
|
|
else if ( auto mem = std::get_if<variable::memory_t>( &var.descriptor ) )
|
|
{
|
|
// If instruction accesses memory:
|
|
//
|
|
if ( it->base->accesses_memory() &&
|
|
( !write || it->base->memory_write ) &&
|
|
( !read || !it->base->memory_write ) )
|
|
{
|
|
// Generate an expression for the pointer.
|
|
//
|
|
auto [base, offset] = it->memory_location();
|
|
pointer ptr = { tracer->trace( { it, base } ) + offset };
|
|
|
|
// Calculate displacement.
|
|
//
|
|
access_details details;
|
|
fill_displacement( &details, ptr, mem->base, tracer, xblock );
|
|
|
|
// If pointers can indeed overlap:
|
|
//
|
|
if ( details )
|
|
{
|
|
// Fill read/write.
|
|
//
|
|
details.read = it->base->reads_memory();
|
|
details.write = it->base->writes_memory();
|
|
|
|
// If offset is unknown, return as is.
|
|
//
|
|
if ( details.is_unknown() )
|
|
return details;
|
|
|
|
// Check if within boundaries, set bit count and return if so.
|
|
//
|
|
bitcnt_t low_offset = details.bit_offset;
|
|
bitcnt_t high_offset = low_offset + it->access_size();
|
|
if ( low_offset < mem->bit_count && high_offset > 0 )
|
|
{
|
|
details.bit_count = it->access_size();
|
|
return details;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If external call:
|
|
//
|
|
if ( it->base->is_branching_real() )
|
|
{
|
|
// Get calling convention.
|
|
//
|
|
call_convention cc = it.container->owner->get_cconv( it->vip );
|
|
|
|
// If variable is a register:
|
|
//
|
|
if ( var.is_register() )
|
|
{
|
|
auto& reg = var.reg();
|
|
|
|
// If $sp, indicate read from:
|
|
//
|
|
if ( reg.is_stack_pointer() )
|
|
{
|
|
if ( write ) return {};
|
|
return {
|
|
.bit_offset = 0,
|
|
.bit_count = reg.bit_count,
|
|
.read = true,
|
|
.write = false
|
|
};
|
|
}
|
|
|
|
// If exiting the virtual machine:
|
|
//
|
|
if ( it->base == &ins::vexit )
|
|
{
|
|
// If retval register, indicate read from:
|
|
//
|
|
for ( const register_desc& retval : it.container->owner->routine_convention.retval_registers )
|
|
{
|
|
if ( retval.overlaps( reg ) )
|
|
{
|
|
if ( write ) return {};
|
|
return {
|
|
.bit_offset = retval.bit_offset - reg.bit_offset,
|
|
.bit_count = retval.bit_count,
|
|
.read = true,
|
|
.write = false
|
|
};
|
|
}
|
|
}
|
|
|
|
// If volatile register, indicate discarded:
|
|
//
|
|
for ( const register_desc& retval : it.container->owner->routine_convention.volatile_registers )
|
|
{
|
|
if ( retval.overlaps( reg ) )
|
|
{
|
|
if ( read ) return {};
|
|
return { .bit_offset = 0, .bit_count = reg.bit_count, .read = false, .write = true };
|
|
}
|
|
}
|
|
|
|
// If virtual register, indicate discarded:
|
|
//
|
|
if ( reg.is_virtual() )
|
|
{
|
|
if ( read ) return {};
|
|
return { .bit_offset = 0, .bit_count = reg.bit_count, .read = false, .write = true };
|
|
}
|
|
|
|
// Otherwise indicate read from.
|
|
//
|
|
if ( write ) return {};
|
|
return {
|
|
.bit_offset = 0,
|
|
.bit_count = reg.bit_count,
|
|
.read = true,
|
|
.write = false
|
|
};
|
|
}
|
|
|
|
// If not only looking for read access, check if register is written to.
|
|
//
|
|
access_details wdetails = {};
|
|
if ( !read )
|
|
{
|
|
for ( const register_desc& param : cc.volatile_registers )
|
|
{
|
|
if ( param.overlaps( reg ) )
|
|
{
|
|
wdetails.bit_offset = param.bit_offset - reg.bit_offset;
|
|
wdetails.bit_count = param.bit_count;
|
|
wdetails.write = true;
|
|
break;
|
|
}
|
|
}
|
|
for ( const register_desc& retval : cc.retval_registers )
|
|
{
|
|
if ( retval.overlaps( reg ) )
|
|
{
|
|
wdetails.bit_offset = retval.bit_offset - reg.bit_offset;
|
|
wdetails.bit_count = retval.bit_count;
|
|
wdetails.write = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If not only looking for write access, check if register is read from.
|
|
//
|
|
access_details rdetails = {};
|
|
if ( !write )
|
|
{
|
|
for ( const register_desc& param : cc.param_registers )
|
|
{
|
|
if ( param.overlaps( reg ) )
|
|
{
|
|
rdetails.bit_offset = param.bit_offset - reg.bit_offset;
|
|
rdetails.bit_count = param.bit_count;
|
|
rdetails.read = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Merge rdetails and wdetails, return.
|
|
//
|
|
if ( !wdetails ) return rdetails;
|
|
if ( !rdetails ) return wdetails;
|
|
return {
|
|
.bit_offset = std::min( wdetails.bit_offset, rdetails.bit_offset ),
|
|
.bit_count = std::max( wdetails.bit_count, rdetails.bit_count ),
|
|
.read = true,
|
|
.write = true
|
|
};
|
|
}
|
|
// If variable is memory:
|
|
//
|
|
else
|
|
{
|
|
auto& mem = var.mem();
|
|
|
|
// If vmexit, declared trashed if below or at the shadow space:
|
|
//
|
|
if ( it->base == &ins::vexit ? it.container->owner->routine_convention.purge_stack : cc.purge_stack )
|
|
{
|
|
// Determine the limit of the stack memory owned by this routine.
|
|
//
|
|
symbolic::expression limit =
|
|
tracer->trace( { it, REG_SP } ) +
|
|
it.container->sp_offset +
|
|
cc.shadow_space;
|
|
|
|
// Calculate the displacement, if constant below 0, declare trashed.
|
|
//
|
|
access_details details;
|
|
fill_displacement( &details, mem.base, pointer{ limit }, tracer, xblock );
|
|
if ( !details.is_unknown() && ( details.bit_offset + var.bit_count() ) <= 0 )
|
|
{
|
|
if ( read ) return {};
|
|
return { .bit_offset = 0, .bit_count = var.bit_count(), .read = false, .write = true };
|
|
}
|
|
}
|
|
|
|
// Report unknown access: (TODO: Proper parsing!)
|
|
// - We can estimate usage based on registers passed, maybe?
|
|
//
|
|
return { .bit_count = var.bit_count(), .unknown = true };
|
|
}
|
|
}
|
|
|
|
// No access case.
|
|
//
|
|
return {};
|
|
}
|
|
|
|
// Constructs by iterator and the variable descriptor itself.
|
|
//
|
|
variable::variable( const il_const_iterator& it, descriptor_t desc ) :
|
|
descriptor( std::move( desc ) )
|
|
{
|
|
// If read-only register, remove the iterator.
|
|
//
|
|
if ( is_register() && reg().is_read_only() ) bind( {} );
|
|
else bind( it );
|
|
|
|
// Validate the variable.
|
|
//
|
|
is_valid( true );
|
|
}
|
|
|
|
// Construct free-form with only the descriptor itself.
|
|
//
|
|
variable::variable( descriptor_t desc )
|
|
: variable( free_form_iterator, std::move( desc ) ) {}
|
|
|
|
// Returns whether the variable is valid or not.
|
|
//
|
|
bool variable::is_valid( bool force ) const
|
|
{
|
|
#define validate(...) { if( force ) fassert(__VA_ARGS__); else if( !(__VA_ARGS__) ) return false; }
|
|
// If register:
|
|
//
|
|
if ( auto* reg = std::get_if<register_t>( &descriptor ) )
|
|
{
|
|
// Iterator must be valid if not read-only.
|
|
//
|
|
validate( at.is_valid() || reg->is_read_only() );
|
|
|
|
// Redirect to register descriptor validation.
|
|
//
|
|
return reg->is_valid( force );
|
|
}
|
|
// If memory:
|
|
//
|
|
else
|
|
{
|
|
auto& mem = std::get<memory_t>( descriptor );
|
|
|
|
// Iterator must be valid.
|
|
//
|
|
validate( at.is_valid() );
|
|
|
|
// Must have a valid pointer of 64 bits.
|
|
//
|
|
validate( mem.base.base && mem.base.base.size() == 64 );
|
|
|
|
// Bit count should be within (0, 64] and byte-addressable.
|
|
//
|
|
validate( 0 < mem.bit_count && mem.bit_count <= 64 && ( mem.bit_count & 7 ) == 0 );
|
|
|
|
return true;
|
|
}
|
|
#undef validate
|
|
}
|
|
|
|
// Returns whether it is bound to a free-form iterator or not.
|
|
//
|
|
bool variable::is_free_form() const
|
|
{
|
|
return at == free_form_iterator;
|
|
}
|
|
|
|
// Conversion to symbolic expression.
|
|
//
|
|
expression variable::to_expression( bool unpack ) const
|
|
{
|
|
// If memory, return as is.
|
|
//
|
|
if ( is_memory() )
|
|
return { *this, mem().bit_count };
|
|
|
|
// If not register (so invalid), return null.
|
|
//
|
|
if ( !is_register() )
|
|
return {};
|
|
|
|
// If no unpacking requested, return as is.
|
|
//
|
|
const register_desc& src = reg();
|
|
if ( !unpack )
|
|
return { *this, src.bit_count };
|
|
|
|
// Extend to 64-bits with offset set at 0, shift it and
|
|
// mask it to experss the value of original register.
|
|
//
|
|
expression&& tmp = variable{ at, register_desc{ src.flags, src.local_id, 64, 0, src.architecture } }.to_expression( false );
|
|
return ( src.bit_offset ? tmp >> src.bit_offset : tmp ).resize( src.bit_count );
|
|
}
|
|
|
|
// Conversion to human-readable format.
|
|
//
|
|
std::string variable::to_string() const
|
|
{
|
|
// If invalid, return null.
|
|
//
|
|
if ( !is_valid() )
|
|
return "null";
|
|
|
|
// Allocate temporary storage for the base name.
|
|
//
|
|
std::string base;
|
|
|
|
// If memory:
|
|
//
|
|
if ( auto* mem = std::get_if<memory_t>( &descriptor ) )
|
|
{
|
|
// Indicate dereferencing of the pointer expression.
|
|
//
|
|
base = format::str( "[%s]", mem->decay() );
|
|
|
|
// Prefix with read size:
|
|
//
|
|
switch ( mem->bit_count )
|
|
{
|
|
case 1*8: base = "byte" + base; break;
|
|
case 2*8: base = "word" + base; break;
|
|
case 4*8: base = "dword" + base; break;
|
|
case 6*8: base = "fword" + base; break;
|
|
case 8*8: base = "qword" + base; break;
|
|
default: base = "u" + std::to_string( mem->bit_count ) + base; break;
|
|
}
|
|
}
|
|
// If register:
|
|
//
|
|
else
|
|
{
|
|
// Redirect to register_desc string conversion.
|
|
//
|
|
base = std::get<register_t>( descriptor ).to_string();
|
|
}
|
|
|
|
// Indicate branch-dependence.
|
|
//
|
|
if ( is_branch_dependant )
|
|
base += "...";
|
|
|
|
// If no valid iterator, return as is.
|
|
//
|
|
if ( !at.is_valid() )
|
|
return base;
|
|
|
|
// If dummy iterator, return with free-indicator appended.
|
|
//
|
|
if ( at == free_form_iterator )
|
|
return "%" + base;
|
|
|
|
// Append the block identifier.
|
|
//
|
|
base = format::str( "%s#0x%llx", base, at.container->entry_vip );
|
|
|
|
// Append the stream index and return.
|
|
//
|
|
if ( at.is_begin() ) return base + "?";
|
|
else if ( at.is_end() ) return base + "*";
|
|
else return base + "." + std::to_string( std::distance( at.container->begin(), at ) );
|
|
}
|
|
|
|
// Packs all the variables in the expression where it'd be optimal.
|
|
//
|
|
expression variable::pack_all( const expression& ref )
|
|
{
|
|
// List of ideal packers.
|
|
//
|
|
static constexpr std::pair<bitcnt_t, bitcnt_t> ideal_packers[] =
|
|
{
|
|
{ 1, 0 }, // Any boolean register.
|
|
{ 8, 0 }, // Low byte, e.g. AL.
|
|
{ 8, 8 }, // High byte, e.g. AH.
|
|
{ 16, 0 }, // Low word, e.g. AX.
|
|
{ 32, 0 }, // Low dword, e.g. EAX.
|
|
};
|
|
|
|
// Copy expression and recurse into it.
|
|
//
|
|
expression exp = ref;
|
|
exp.transform( [ ] ( expression& exp )
|
|
{
|
|
// Skip if expression has any known 1s.
|
|
//
|
|
if ( exp.known_one() )
|
|
return;
|
|
|
|
// Skip if expression does not have exactly one variable.
|
|
//
|
|
if ( exp.count_unique_variables() != 1 )
|
|
return;
|
|
|
|
// Check if the unknown mask matches that of an ideal packer.
|
|
//
|
|
auto it = std::find_if( ideal_packers, std::end( ideal_packers ), [ & ] ( auto& pair )
|
|
{
|
|
return math::fill( pair.first ) == exp.unknown_mask();
|
|
} );
|
|
if ( it == std::end( ideal_packers ) )
|
|
return;
|
|
|
|
// Clone and resize the expression.
|
|
//
|
|
auto exp_resized = exp.clone().resize( it->first );
|
|
|
|
// If top node is not __ucast, skip.
|
|
//
|
|
if ( exp_resized.op != math::operator_id::ucast )
|
|
return;
|
|
|
|
// Until we reach the end of the list and the size is the same:
|
|
//
|
|
for ( ; it != std::end( ideal_packers ) && it->first == exp_resized.size(); it++ )
|
|
{
|
|
auto node = exp_resized.lhs;
|
|
|
|
// If expected bit offset is non-zero:
|
|
//
|
|
if ( it->second != 0 )
|
|
{
|
|
// If node is not shift right, skip.
|
|
//
|
|
if ( node->op != math::operator_id::shift_right )
|
|
continue;
|
|
|
|
// If node is not shifting as expected, skip.
|
|
//
|
|
if ( !node->rhs->equals( it->second ) )
|
|
continue;
|
|
|
|
// Skip to the real operand.
|
|
//
|
|
node = node->lhs;
|
|
}
|
|
|
|
// Skip if top node is not a variable.
|
|
//
|
|
if ( !node->is_variable() )
|
|
continue;
|
|
|
|
// Break if the variable is not a register.
|
|
//
|
|
const variable& var = node->uid.get<variable>();
|
|
if ( !var.is_register() )
|
|
break;
|
|
|
|
// Break if cannot be fit.
|
|
//
|
|
const register_desc& reg = var.reg();
|
|
if ( reg.bit_count < it->first )
|
|
break;
|
|
|
|
// Found a match, rewrite the expression and stop iterating.
|
|
//
|
|
variable var_new = var;
|
|
var_new.reg().bit_count = it->first;
|
|
var_new.reg().bit_offset += it->second;
|
|
exp = expression{ var_new, it->first }.resize( exp.size() );
|
|
break;
|
|
}
|
|
} );
|
|
return exp;
|
|
}
|
|
|
|
// Checks if the variable is read by / written by the given instruction,
|
|
// returns nullopt it could not be known at compile-time, otherwise the
|
|
// access details as described by access_details. Tracer is used for
|
|
// pointer resolving, if nullptr passed will use default tracer.
|
|
//
|
|
access_details variable::read_by( const il_const_iterator& it, tracer* tr, bool xblock ) const
|
|
{
|
|
tracer default_tracer;
|
|
if ( !tr ) tr = &default_tracer;
|
|
return test_access( *this, it, tr ? tr : &default_tracer, false, true, xblock );
|
|
}
|
|
access_details variable::written_by( const il_const_iterator& it, tracer* tr, bool xblock ) const
|
|
{
|
|
tracer default_tracer;
|
|
if ( !tr ) tr = &default_tracer;
|
|
return test_access( *this, it, tr ? tr : &default_tracer, true, false, xblock );
|
|
}
|
|
access_details variable::accessed_by( const il_const_iterator& it, tracer* tr, bool xblock ) const
|
|
{
|
|
tracer default_tracer;
|
|
if ( !tr ) tr = &default_tracer;
|
|
return test_access( *this, it, tr ? tr : &default_tracer, false, false, xblock );
|
|
}
|
|
}; |