mirror of
https://github.com/vtil-project/VTIL-Core
synced 2026-08-17 08:23:03 -04:00
Implemented symbolic::context for fast bitwise access to registers.
This commit is contained in:
parent
22a6aca8a7
commit
565b8b43ab
5 changed files with 536 additions and 231 deletions
|
|
@ -10,6 +10,7 @@
|
|||
#include "../../routine/instruction.hpp"
|
||||
#include "../../routine/serialization.hpp"
|
||||
#include "../../symex/memory.hpp"
|
||||
#include "../../symex/context.hpp"
|
||||
#include "../../symex/pointer.hpp"
|
||||
#include "../../symex/variable.hpp"
|
||||
#include "../../symex/translation.hpp"
|
||||
|
|
|
|||
188
VTIL-Architecture/symex/context.cpp
Normal file
188
VTIL-Architecture/symex/context.cpp
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
// 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 VTIL Project 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/math>
|
||||
#include "context.hpp"
|
||||
|
||||
namespace vtil::symbolic
|
||||
{
|
||||
// Checks if the symbolic context contains any writes to the given region described by the register desc.
|
||||
//
|
||||
bool context::contains( const register_desc& desc ) const
|
||||
{
|
||||
// If identifier is not in the store, return false.
|
||||
//
|
||||
auto it = value_map.find( desc );
|
||||
if ( it == value_map.end() )
|
||||
return false;
|
||||
|
||||
// Enumerate each bit set within (size+offset, 0]:
|
||||
//
|
||||
bool found = false;
|
||||
math::bit_enum( it->second.bitmap & math::fill( desc.bit_count + desc.bit_offset ), [ & ] ( bitcnt_t i )
|
||||
{
|
||||
// If value extends into the region, declare found.
|
||||
//
|
||||
const expression::reference& value = it->second.linear_store[ i ];
|
||||
if ( ( value.size() + i ) > desc.bit_offset )
|
||||
found = true;
|
||||
} );
|
||||
return found;
|
||||
}
|
||||
|
||||
// Reads the value of the given region described by the register desc.
|
||||
//
|
||||
expression::reference context::read( const register_desc& desc, const il_const_iterator& reference_iterator ) const
|
||||
{
|
||||
// If identifier is not in the store, return default.
|
||||
//
|
||||
auto it = value_map.find( desc );
|
||||
if ( it == value_map.end() )
|
||||
return CTX( reference_iterator )[ desc ];
|
||||
|
||||
// Allocate storage for result and create masks.
|
||||
//
|
||||
uint64_t known_mask = 0;
|
||||
uint64_t read_mask = desc.get_mask();
|
||||
expression::reference result = nullptr;
|
||||
|
||||
// Enumerate each bit set within (size+offset, 0]:
|
||||
//
|
||||
math::bit_enum( it->second.bitmap & math::fill( desc.bit_count + desc.bit_offset ), [ & ] ( bitcnt_t i )
|
||||
{
|
||||
// If value extends into the region:
|
||||
//
|
||||
const expression::reference& value = it->second.linear_store[ i ];
|
||||
if ( ( value.size() + i ) > desc.bit_offset )
|
||||
{
|
||||
// Set known mask.
|
||||
//
|
||||
known_mask |= math::fill( value.size(), i );
|
||||
|
||||
// Adjust the value.
|
||||
//
|
||||
expression::reference adjusted = value;
|
||||
if ( i > desc.bit_offset ) adjusted.resize( desc.bit_count ) <<= ( i - desc.bit_offset );
|
||||
else if ( i < desc.bit_offset ) adjusted >>= ( desc.bit_offset - i ), adjusted.resize( desc.bit_count );
|
||||
else adjusted.resize( desc.bit_count );
|
||||
|
||||
// Append to the result.
|
||||
//
|
||||
if ( result ) result |= std::move( adjusted );
|
||||
else result = std::move( adjusted );
|
||||
}
|
||||
} );
|
||||
|
||||
// If no bits set in known mask, return default.
|
||||
//
|
||||
if ( !known_mask )
|
||||
return CTX( reference_iterator )[ desc ];
|
||||
|
||||
// If all bits set in known mask, return as is.
|
||||
//
|
||||
if ( ( known_mask & read_mask ) == read_mask )
|
||||
return result;
|
||||
|
||||
// Or with the bits that we do not know and return.
|
||||
//
|
||||
return result | ( variable{ reference_iterator, desc.select( 64, 0 ) }.to_expression() & ( read_mask & ~known_mask ) ) >> desc.bit_offset;
|
||||
}
|
||||
|
||||
// Writes the given value to the region described by the register desc.
|
||||
//
|
||||
void context::write( const register_desc& desc, expression::reference value )
|
||||
{
|
||||
// Find the register in the map and determine limit of the descriptor.
|
||||
//
|
||||
auto& context = value_map[ desc ];
|
||||
bitcnt_t reg_end = desc.bit_count + desc.bit_offset;
|
||||
|
||||
// Push left (size+offset, offset].
|
||||
//
|
||||
math::bit_enum( context.bitmap & desc.get_mask(), [ & ] ( bitcnt_t i )
|
||||
{
|
||||
// Reset the bit, and move the value.
|
||||
//
|
||||
expression::reference stored_value = std::exchange( context.linear_store[ i ], nullptr );
|
||||
bitcnt_t value_end = stored_value.size() + i;
|
||||
math::bit_reset( context.bitmap, i );
|
||||
|
||||
// If value extends beyond the region we're overwriting:
|
||||
//
|
||||
if ( value_end > reg_end )
|
||||
{
|
||||
// Shift the value and place it at the border.
|
||||
//
|
||||
auto& ref = context.linear_store[ reg_end ];
|
||||
dassert( !ref );
|
||||
|
||||
ref = std::move( stored_value ) >> ( reg_end - i );
|
||||
ref.resize( value_end - reg_end );
|
||||
math::bit_set( context.bitmap, reg_end );
|
||||
}
|
||||
} );
|
||||
|
||||
// Push right (offset, 0].
|
||||
//
|
||||
math::bit_enum( context.bitmap & math::fill( desc.bit_offset, 0 ), [ & ] ( bitcnt_t i )
|
||||
{
|
||||
// If value extends into the region we're overwriting:
|
||||
//
|
||||
expression::reference& stored_value = context.linear_store[ i ];
|
||||
bitcnt_t value_end = stored_value.size() + i;
|
||||
|
||||
if ( value_end > desc.bit_offset )
|
||||
{
|
||||
// If value extends beyond the region we're overwriting:
|
||||
//
|
||||
if ( value_end > reg_end )
|
||||
{
|
||||
auto& ext = context.linear_store[ reg_end ];
|
||||
dassert( !ext );
|
||||
|
||||
ext = stored_value >> ( reg_end - i );
|
||||
ext.resize( value_end - reg_end );
|
||||
math::bit_set( context.bitmap, reg_end );
|
||||
}
|
||||
|
||||
// Resize the value.
|
||||
//
|
||||
stored_value.resize( desc.bit_offset - i );
|
||||
}
|
||||
} );
|
||||
|
||||
// Write the value.
|
||||
//
|
||||
value.resize( desc.bit_count );
|
||||
|
||||
auto& res = context.linear_store[ desc.bit_offset ];
|
||||
dassert( !res );
|
||||
|
||||
res = std::move( value );
|
||||
math::bit_set( context.bitmap, desc.bit_offset );
|
||||
}
|
||||
};
|
||||
80
VTIL-Architecture/symex/context.hpp
Normal file
80
VTIL-Architecture/symex/context.hpp
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
// 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 VTIL Project 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.
|
||||
//
|
||||
#pragma once
|
||||
#include <vtil/utility>
|
||||
#include <unordered_map>
|
||||
#include "variable.hpp"
|
||||
#include "../arch/register_desc.hpp"
|
||||
|
||||
namespace vtil::symbolic
|
||||
{
|
||||
struct context
|
||||
{
|
||||
// Common typedefs.
|
||||
//
|
||||
struct segmented_value
|
||||
{
|
||||
symbolic::expression::reference linear_store[ 64 ] = { nullptr };
|
||||
uint64_t bitmap = 0;
|
||||
};
|
||||
using store_type = std::unordered_map<register_desc::weak_id, segmented_value, hasher<>>;
|
||||
|
||||
// The register state.
|
||||
//
|
||||
store_type value_map;
|
||||
|
||||
// Default copy/move/construct.
|
||||
//
|
||||
context() = default;
|
||||
context( context&& ) = default;
|
||||
context( const context& ) = default;
|
||||
context& operator=( context&& ) = default;
|
||||
context& operator=( const context& ) = default;
|
||||
|
||||
// Wrap around the store type.
|
||||
//
|
||||
auto begin() { return value_map.begin(); }
|
||||
auto end() { return value_map.end(); }
|
||||
auto begin() const { return value_map.cbegin(); }
|
||||
auto end() const { return value_map.cend(); }
|
||||
size_t size() const { return value_map.size(); }
|
||||
void reset() { value_map.clear(); }
|
||||
|
||||
// Checks if the symbolic context contains any writes to the given region described by the register desc.
|
||||
//
|
||||
bool contains( const register_desc& desc ) const;
|
||||
|
||||
// Reads the value of the given region described by the register desc.
|
||||
//
|
||||
expression::reference read( const register_desc& desc, const il_const_iterator& reference_iterator = symbolic::free_form_iterator ) const;
|
||||
|
||||
// Writes the given value to the region described by the register desc.
|
||||
//
|
||||
void write( const register_desc& desc, expression::reference value );
|
||||
};
|
||||
};
|
||||
259
VTIL-Architecture/symex/memory.cpp
Normal file
259
VTIL-Architecture/symex/memory.cpp
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
// 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 VTIL Project 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/math>
|
||||
#include "memory.hpp"
|
||||
|
||||
namespace vtil::symbolic
|
||||
{
|
||||
// Checks if the symbolic memory contains any writes to the given memory region.
|
||||
//
|
||||
trilean memory::contains( const pointer& ptr, bitcnt_t size ) const
|
||||
{
|
||||
uint64_t mask_value = math::fill( size );
|
||||
|
||||
// For each entry, iterating backwards:
|
||||
//
|
||||
for ( auto it = value_map.rbegin(); it != value_map.rend(); it++ )
|
||||
{
|
||||
// If pointer cannot overlap lookup, skip.
|
||||
//
|
||||
if ( !it->first.can_overlap( ptr ) )
|
||||
continue;
|
||||
|
||||
// Calculate displacement, if unknown return unknown.
|
||||
//
|
||||
std::optional byte_distance = it->first - ptr;
|
||||
if ( !byte_distance )
|
||||
return trilean::unknown;
|
||||
|
||||
// Calculate relative mask, return true if overlapping.
|
||||
//
|
||||
bitcnt_t bit_distance = math::narrow_cast< bitcnt_t >( *byte_distance * 8 );
|
||||
uint64_t relative_mask = math::fill( it->second.size(), bit_distance );
|
||||
if ( relative_mask & mask_value )
|
||||
return true;
|
||||
}
|
||||
|
||||
// None found, return false.
|
||||
//
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reads N bits from the given pointer, returns null reference if alias failure occurs.
|
||||
//
|
||||
expression::reference memory::read( const pointer& ptr, bitcnt_t size, const il_const_iterator& reference_iterator ) const
|
||||
{
|
||||
uint64_t mask_pending = math::fill( size );
|
||||
stack_vector<std::pair<bitcnt_t, expression::reference>, 8> merge_list;
|
||||
|
||||
// For each entry, iterating backwards:
|
||||
//
|
||||
for ( auto it = value_map.rbegin(); it != value_map.rend() && mask_pending; it++ )
|
||||
{
|
||||
// If pointer cannot overlap lookup, skip.
|
||||
//
|
||||
if ( !it->first.can_overlap( ptr ) )
|
||||
continue;
|
||||
|
||||
// Calculate displacement, if unknown:
|
||||
//
|
||||
std::optional byte_distance = it->first - ptr;
|
||||
if ( !byte_distance )
|
||||
{
|
||||
// If not relaxed aliasing, indicate alias failure by returning null.
|
||||
//
|
||||
if ( !relaxed_aliasing )
|
||||
return nullptr;
|
||||
|
||||
// Otherwise, return default value, cannot be determined.
|
||||
//
|
||||
merge_list.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate relative mask, skip if not overlapping.
|
||||
//
|
||||
bitcnt_t bit_distance = math::narrow_cast<bitcnt_t>( *byte_distance * 8 );
|
||||
uint64_t relative_mask = math::fill( it->second.size(), bit_distance );
|
||||
if ( !( relative_mask & mask_pending ) )
|
||||
continue;
|
||||
|
||||
// Add into merge list, clear the mask.
|
||||
//
|
||||
merge_list.emplace_back( bit_distance, it->second );
|
||||
mask_pending &= ~relative_mask;
|
||||
}
|
||||
|
||||
// If no overlapping keys found, return default.
|
||||
//
|
||||
if ( merge_list.empty() )
|
||||
return MEMORY( reference_iterator )( ptr, size );
|
||||
|
||||
// Declare common bit selector.
|
||||
//
|
||||
constexpr auto select = [ ] ( symbolic::expression::reference& value, bitcnt_t size, bitcnt_t offset )
|
||||
{
|
||||
if ( offset < 0 ) value >>= -offset, value.resize( size );
|
||||
else if ( offset > 0 ) value.resize( size ) <<= offset;
|
||||
else value.resize( size );
|
||||
};
|
||||
|
||||
// If single overlapping key with no pending bits, return as is.
|
||||
//
|
||||
if ( !mask_pending && merge_list.size() == 1 )
|
||||
{
|
||||
auto&& [dst, value] = std::move( merge_list[ 0 ] );
|
||||
select( value, size, dst );
|
||||
return value;
|
||||
}
|
||||
|
||||
// Merge all in a single expression and return.
|
||||
//
|
||||
expression::reference result = mask_pending
|
||||
? MEMORY( reference_iterator )( ptr, size )
|
||||
: expression{ 0, size };
|
||||
|
||||
for ( auto& [dst, value] : merge_list )
|
||||
{
|
||||
select( value, size, dst );
|
||||
result |= std::move( value );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Writes the given value to the pointer, returns null reference if alias failure occurs.
|
||||
//
|
||||
optional_reference<expression::reference> memory::write( const pointer& ptr, deferred_value<expression::reference> value, bitcnt_t size )
|
||||
{
|
||||
uint64_t mask_pending = math::fill( size );
|
||||
stack_vector<std::pair<bitcnt_t, store_type::iterator>, 8> acquisition_list;
|
||||
|
||||
// For each entry, iterating backwards:
|
||||
//
|
||||
for ( auto it = value_map.rbegin(); it != value_map.rend() && mask_pending; it++ )
|
||||
{
|
||||
// If pointer cannot overlap lookup, skip.
|
||||
//
|
||||
if ( !it->first.can_overlap( ptr ) )
|
||||
continue;
|
||||
|
||||
// Calculate displacement, if unknown:
|
||||
//
|
||||
std::optional byte_distance = it->first - ptr;
|
||||
if ( !byte_distance )
|
||||
{
|
||||
// If not relaxed aliasing, indicate alias failure by returning null.
|
||||
//
|
||||
if ( !relaxed_aliasing )
|
||||
return std::nullopt;
|
||||
|
||||
// Otherwise, insert at the end, overlaps can't be determined.
|
||||
//
|
||||
acquisition_list.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate relative mask, skip if not overlapping.
|
||||
//
|
||||
bitcnt_t bit_distance = math::narrow_cast<bitcnt_t>( *byte_distance * 8 );
|
||||
uint64_t relative_mask = math::fill( it->second.size(), bit_distance );
|
||||
if ( !( relative_mask & mask_pending ) )
|
||||
continue;
|
||||
|
||||
// Add into acquisition list, clear the mask.
|
||||
//
|
||||
acquisition_list.emplace_back( bit_distance, std::prev( it.base() ) );
|
||||
mask_pending &= ~relative_mask;
|
||||
}
|
||||
|
||||
// For each iterator we should acquire bits from:
|
||||
//
|
||||
for ( auto& [dst, it] : acquisition_list )
|
||||
{
|
||||
// If low bits start at or above our pointer:
|
||||
// | v v v v | v v v v |
|
||||
// | a b c d ... | a b c d ... |
|
||||
//
|
||||
if ( dst >= 0 )
|
||||
{
|
||||
bitcnt_t strip_low_cnt = size - dst;
|
||||
bitcnt_t new_size = it->second->size() - strip_low_cnt;
|
||||
|
||||
// If value is completely overwritten, erase and continue.
|
||||
//
|
||||
if ( new_size <= 0 )
|
||||
{
|
||||
value_map.erase( it );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Shift and resize the entry.
|
||||
//
|
||||
it->first = std::move( it->first ) + ( strip_low_cnt / 8 );
|
||||
it->second >>= strip_low_cnt;
|
||||
it->second.resize( new_size );
|
||||
}
|
||||
// If high bits end before or at our region limits:
|
||||
// | v v v v | v v v v |
|
||||
// | ... a b c d | ... a b c d |
|
||||
//
|
||||
else if ( ( size - dst ) >= it->second.size() )
|
||||
{
|
||||
// Shift and resize the entry.
|
||||
//
|
||||
it->second.resize( -dst );
|
||||
}
|
||||
// Split the region:
|
||||
// | v v |
|
||||
// | ... a b c d ... |
|
||||
//
|
||||
else
|
||||
{
|
||||
bitcnt_t low_size = -dst;
|
||||
bitcnt_t high_offset = low_size + size;
|
||||
bitcnt_t high_size = it->second.size() - high_offset;
|
||||
|
||||
// Split high value.
|
||||
//
|
||||
value_map.emplace(
|
||||
it,
|
||||
it->first + ( high_offset / 8 ),
|
||||
( it->second >> high_offset ).resize( high_size )
|
||||
);
|
||||
|
||||
// Resize low value.
|
||||
//
|
||||
it->second.resize( low_size );
|
||||
}
|
||||
}
|
||||
|
||||
// Insert new value.
|
||||
//
|
||||
return value_map.emplace_back( ptr, value.get() ).second;
|
||||
}
|
||||
};
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
//
|
||||
#pragma once
|
||||
#include <vtil/utility>
|
||||
#include <vtil/math>
|
||||
#include <list>
|
||||
#include "pointer.hpp"
|
||||
#include "variable.hpp"
|
||||
#include "../arch/register_desc.hpp"
|
||||
|
|
@ -40,21 +40,16 @@ namespace vtil::symbolic
|
|||
//
|
||||
using store_entry = std::pair<pointer, expression::reference>;
|
||||
using store_type = std::list<store_entry>;
|
||||
|
||||
// The iterator to bind every variable onto.
|
||||
//
|
||||
il_const_iterator reference_iterator;
|
||||
|
||||
// The memory state.
|
||||
//
|
||||
bool relaxed_aliasing;
|
||||
store_type value_map;
|
||||
|
||||
// Default constructor, optionally takes a boolean to indicate relaxed aliasing
|
||||
// and a reference iterator to be used when creating variables.
|
||||
// Default constructor, optionally takes a boolean to indicate relaxed aliasing.
|
||||
//
|
||||
memory( bool relaxed_aliasing = false, il_const_iterator reference_iterator = free_form_iterator )
|
||||
: relaxed_aliasing( relaxed_aliasing ), reference_iterator( std::move( reference_iterator ) ) {}
|
||||
memory( bool relaxed_aliasing = false )
|
||||
: relaxed_aliasing( relaxed_aliasing ) {}
|
||||
|
||||
// Default copy/move.
|
||||
//
|
||||
|
|
@ -74,233 +69,15 @@ namespace vtil::symbolic
|
|||
|
||||
// Checks if the symbolic memory contains any writes to the given memory region.
|
||||
//
|
||||
trilean contains( const pointer& ptr, bitcnt_t size ) const
|
||||
{
|
||||
uint64_t mask_value = math::fill( size );
|
||||
|
||||
// For each entry, iterating backwards:
|
||||
//
|
||||
for ( auto it = value_map.rbegin(); it != value_map.rend(); it++ )
|
||||
{
|
||||
// If pointer cannot overlap lookup, skip.
|
||||
//
|
||||
if ( !it->first.can_overlap( ptr ) )
|
||||
continue;
|
||||
|
||||
// Calculate displacement, if unknown return unknown.
|
||||
//
|
||||
std::optional byte_distance = it->first - ptr;
|
||||
if ( !byte_distance )
|
||||
return trilean::unknown;
|
||||
|
||||
// Calculate relative mask, return true if overlapping.
|
||||
//
|
||||
bitcnt_t bit_distance = math::narrow_cast<bitcnt_t>( *byte_distance * 8 );
|
||||
uint64_t relative_mask = math::fill( it->second.size(), bit_distance );
|
||||
if ( relative_mask & mask_value )
|
||||
return true;
|
||||
}
|
||||
|
||||
// None found, return false.
|
||||
//
|
||||
return false;
|
||||
}
|
||||
trilean contains( const pointer& ptr, bitcnt_t size ) const;
|
||||
|
||||
// Reads N bits from the given pointer, returns null reference if alias failure occurs.
|
||||
//
|
||||
expression::reference read( const pointer& ptr, bitcnt_t size ) const
|
||||
{
|
||||
uint64_t mask_pending = math::fill( size );
|
||||
stack_vector<std::pair<bitcnt_t, expression::reference>, 8> merge_list;
|
||||
|
||||
// For each entry, iterating backwards:
|
||||
//
|
||||
for ( auto it = value_map.rbegin(); it != value_map.rend() && mask_pending; it++ )
|
||||
{
|
||||
// If pointer cannot overlap lookup, skip.
|
||||
//
|
||||
if ( !it->first.can_overlap( ptr ) )
|
||||
continue;
|
||||
|
||||
// Calculate displacement, if unknown:
|
||||
//
|
||||
std::optional byte_distance = it->first - ptr;
|
||||
if ( !byte_distance )
|
||||
{
|
||||
// If not relaxed aliasing, indicate alias failure by returning null.
|
||||
//
|
||||
if ( !relaxed_aliasing )
|
||||
return nullptr;
|
||||
|
||||
// Otherwise, return default value, cannot be determined.
|
||||
//
|
||||
merge_list.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate relative mask, skip if not overlapping.
|
||||
//
|
||||
bitcnt_t bit_distance = math::narrow_cast<bitcnt_t>( *byte_distance * 8 );
|
||||
uint64_t relative_mask = math::fill( it->second.size(), bit_distance );
|
||||
if ( !( relative_mask & mask_pending ) )
|
||||
continue;
|
||||
|
||||
// Add into merge list, clear the mask.
|
||||
//
|
||||
merge_list.emplace_back( bit_distance, it->second );
|
||||
mask_pending &= ~relative_mask;
|
||||
}
|
||||
|
||||
// If no overlapping keys found, return default.
|
||||
//
|
||||
if ( merge_list.empty() )
|
||||
return MEMORY( reference_iterator )( ptr, size );
|
||||
|
||||
// Declare common bit selector.
|
||||
//
|
||||
constexpr auto select = [ ] ( symbolic::expression::reference& value, bitcnt_t size, bitcnt_t offset )
|
||||
{
|
||||
if ( offset < 0 ) value >>= -offset, value.resize( size );
|
||||
else if ( offset > 0 ) value.resize( size ) <<= offset;
|
||||
else value.resize( size );
|
||||
};
|
||||
|
||||
// If single overlapping key with no pending bits, return as is.
|
||||
//
|
||||
if ( !mask_pending && merge_list.size() == 1 )
|
||||
{
|
||||
auto&& [dst, value] = std::move( merge_list[ 0 ] );
|
||||
select( value, size, dst );
|
||||
return value;
|
||||
}
|
||||
|
||||
// Merge all in a single expression and return.
|
||||
//
|
||||
expression::reference result = mask_pending
|
||||
? MEMORY( reference_iterator )( ptr, size )
|
||||
: expression{ 0, size };
|
||||
|
||||
for ( auto& [ dst, value ] : merge_list )
|
||||
{
|
||||
select( value, size, dst );
|
||||
result |= std::move( value );
|
||||
}
|
||||
return result;
|
||||
}
|
||||
expression::reference read( const pointer& ptr, bitcnt_t size, const il_const_iterator& reference_iterator = symbolic::free_form_iterator ) const;
|
||||
|
||||
// Writes the given value to the pointer, returns null reference if alias failure occurs.
|
||||
//
|
||||
optional_reference<expression::reference> write( const pointer& ptr, deferred_view<expression::reference> value, bitcnt_t size )
|
||||
{
|
||||
uint64_t mask_pending = math::fill( size );
|
||||
stack_vector<std::pair<bitcnt_t, store_type::iterator>, 8> acquisition_list;
|
||||
|
||||
// For each entry, iterating backwards:
|
||||
//
|
||||
for ( auto it = value_map.rbegin(); it != value_map.rend() && mask_pending; it++ )
|
||||
{
|
||||
// If pointer cannot overlap lookup, skip.
|
||||
//
|
||||
if ( !it->first.can_overlap( ptr ) )
|
||||
continue;
|
||||
|
||||
// Calculate displacement, if unknown:
|
||||
//
|
||||
std::optional byte_distance = it->first - ptr;
|
||||
if ( !byte_distance )
|
||||
{
|
||||
// If not relaxed aliasing, indicate alias failure by returning null.
|
||||
//
|
||||
if ( !relaxed_aliasing )
|
||||
return std::nullopt;
|
||||
|
||||
// Otherwise, insert at the end, overlaps can't be determined.
|
||||
//
|
||||
acquisition_list.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate relative mask, skip if not overlapping.
|
||||
//
|
||||
bitcnt_t bit_distance = math::narrow_cast<bitcnt_t>( *byte_distance * 8 );
|
||||
uint64_t relative_mask = math::fill( it->second.size(), bit_distance );
|
||||
if ( !( relative_mask & mask_pending ) )
|
||||
continue;
|
||||
|
||||
// Add into acquisition list, clear the mask.
|
||||
//
|
||||
acquisition_list.emplace_back( bit_distance, std::prev( it.base() ) );
|
||||
mask_pending &= ~relative_mask;
|
||||
}
|
||||
|
||||
// For each iterator we should acquire bits from:
|
||||
//
|
||||
for ( auto& [ dst, it ] : acquisition_list )
|
||||
{
|
||||
// If low bits start at or above our pointer:
|
||||
// | v v v v | v v v v |
|
||||
// | a b c d ... | a b c d ... |
|
||||
//
|
||||
if ( dst >= 0 )
|
||||
{
|
||||
bitcnt_t strip_low_cnt = size - dst;
|
||||
bitcnt_t new_size = it->second->size() - strip_low_cnt;
|
||||
|
||||
// If value is completely overwritten, erase and continue.
|
||||
//
|
||||
if ( new_size <= 0 )
|
||||
{
|
||||
value_map.erase( it );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Shift and resize the entry.
|
||||
//
|
||||
it->first = std::move( it->first ) + ( strip_low_cnt / 8 );
|
||||
it->second >>= strip_low_cnt;
|
||||
it->second.resize( new_size );
|
||||
}
|
||||
// If high bits end before or at our region limits:
|
||||
// | v v v v | v v v v |
|
||||
// | ... a b c d | ... a b c d |
|
||||
//
|
||||
else if ( ( size - dst ) >= it->second.size() )
|
||||
{
|
||||
// Shift and resize the entry.
|
||||
//
|
||||
it->second.resize( -dst );
|
||||
}
|
||||
// Split the region:
|
||||
// | v v |
|
||||
// | ... a b c d ... |
|
||||
//
|
||||
else
|
||||
{
|
||||
bitcnt_t low_size = -dst;
|
||||
bitcnt_t high_offset = low_size + size;
|
||||
bitcnt_t high_size = it->second.size() - high_offset;
|
||||
|
||||
// Split high value.
|
||||
//
|
||||
value_map.emplace(
|
||||
it,
|
||||
it->first + ( high_offset / 8 ),
|
||||
( it->second >> high_offset ).resize( high_size )
|
||||
);
|
||||
|
||||
// Resize low value.
|
||||
//
|
||||
it->second.resize( low_size );
|
||||
}
|
||||
}
|
||||
|
||||
// Insert new value.
|
||||
//
|
||||
return value_map.emplace_back( ptr, value.get() ).second;
|
||||
}
|
||||
optional_reference<expression::reference> write( const pointer& ptr, expression::reference value )
|
||||
{
|
||||
return write( ptr, value, value.size() );
|
||||
}
|
||||
optional_reference<expression::reference> write( const pointer& ptr, deferred_value<expression::reference> value, bitcnt_t size );
|
||||
optional_reference<expression::reference> write( const pointer& ptr, expression::reference value ) { return write( ptr, value, value.size() ); }
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue