mirror of
https://github.com/vtil-project/VTIL-Core
synced 2026-08-17 08:23:03 -04:00
337 lines
No EOL
11 KiB
C++
337 lines
No EOL
11 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 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 <variant>
|
|
#include <string>
|
|
#include <vtil/io>
|
|
#include <vtil/utility>
|
|
#include <vtil/symex>
|
|
#include "pointer.hpp"
|
|
#include "../arch/register_desc.hpp"
|
|
#include "../routine/basic_block.hpp"
|
|
|
|
// Forward declare tracer type.
|
|
//
|
|
namespace vtil { struct tracer; };
|
|
|
|
namespace vtil::symbolic
|
|
{
|
|
// Dummy iterator to be used when variable is not being tracked within a block.
|
|
//
|
|
inline const il_const_iterator free_form_iterator = [ ] ()
|
|
{
|
|
// Create a dummy block with a nop and reference it.
|
|
//
|
|
static basic_block dummy_block{ nullptr, 0 };
|
|
return dummy_block.emplace( dummy_block.begin(), &ins::nop );
|
|
}();
|
|
|
|
// Structure describing how an instruction accesses a variable.
|
|
//
|
|
struct access_details
|
|
{
|
|
// Relative offset to the variable, in bits.
|
|
//
|
|
bitcnt_t bit_offset = 0;
|
|
|
|
// Number of bits the instruction wrote at that offset.
|
|
// - Note: Not necessarily all have to be overlapping with the variable.
|
|
//
|
|
bitcnt_t bit_count = 0;
|
|
|
|
// Type of access.
|
|
//
|
|
bool read = false;
|
|
bool write = false;
|
|
bool unknown = false;
|
|
|
|
// Cast to bool to check if non-null access.
|
|
//
|
|
operator bool() const { return bit_count != 0; }
|
|
|
|
// Check if details are unknown.
|
|
//
|
|
bool is_unknown() { return unknown; }
|
|
|
|
// Combines two access details together.
|
|
//
|
|
access_details operator+( const access_details& o ) const
|
|
{
|
|
if ( !o ) return *this;
|
|
if ( !*this ) return o;
|
|
|
|
bitcnt_t bmax = std::max( o.bit_offset + o.bit_count, bit_offset + bit_count );
|
|
bitcnt_t bmin = std::max( o.bit_offset, bit_offset );
|
|
|
|
return {
|
|
.bit_offset = bmin,
|
|
.bit_count = bmax - bmin,
|
|
.read = read || o.read,
|
|
.write = write || o.write,
|
|
.unknown = unknown || o.unknown
|
|
};
|
|
}
|
|
access_details& operator+=( const access_details& o ) { return *this = ( o + *this ); }
|
|
|
|
// String conversion.
|
|
//
|
|
std::string to_string() const
|
|
{
|
|
if ( bit_count == 0 ) return format::str( "None" );
|
|
const char* str;
|
|
if ( read && write ) str = "RW";
|
|
else if ( read ) str = "R";
|
|
else if ( write ) str = "W";
|
|
else str = "?";
|
|
if ( unknown ) return format::str( "Unknown [%s]", str );
|
|
return format::str( "[%s] @%d:%d", str, bit_count, bit_offset );
|
|
}
|
|
};
|
|
|
|
// A pseudo single-static-assignment variable describing the state of a
|
|
// memory location or a register at a given index into the instruction stream.
|
|
//
|
|
struct variable : reducable<variable>
|
|
{
|
|
// If register type, we just need the register descriptor.
|
|
//
|
|
using register_t = register_desc;
|
|
|
|
// If memory type, we need the base register, the offset into it and
|
|
// the size of the variable we're looking up. Since memory has to be
|
|
// addressed in bytes, size is not in number of bits.
|
|
//
|
|
struct memory_t : reducable<memory_t>
|
|
{
|
|
// Absolute pointer as calculated.
|
|
//
|
|
pointer base;
|
|
|
|
// Size of the variable in bits.
|
|
//
|
|
bitcnt_t bit_count;
|
|
|
|
// Construct from base offset and size.
|
|
//
|
|
memory_t( pointer base = {}, bitcnt_t bit_count = 0 )
|
|
: base( std::move( base ) ), bit_count( bit_count ) {}
|
|
|
|
// Add a decay wrapper.
|
|
// - Always return constant since this value should not be modified
|
|
// without recomputation of the xpointers.
|
|
//
|
|
const expression::reference& decay() const { return base.base; }
|
|
|
|
// Declare reduction.
|
|
//
|
|
REDUCE_TO( bit_count, base );
|
|
};
|
|
|
|
// The iterator at which this variable is read at.
|
|
//
|
|
il_const_iterator at = {};
|
|
|
|
// Variant descriptor that holds either one of the variable types.
|
|
//
|
|
using descriptor_t = std::variant<register_t, memory_t>;
|
|
descriptor_t descriptor;
|
|
|
|
// Since SSA constraints are violated if the block is looping,
|
|
// we have to add a hint to declare it branch-dependant where
|
|
// relevant.
|
|
//
|
|
bool is_branch_dependant = false;
|
|
|
|
// Default, null constructor.
|
|
//
|
|
variable() {}
|
|
|
|
// Construct by iterator and the variable descriptor itself.
|
|
//
|
|
variable( const il_const_iterator& it, descriptor_t desc );
|
|
variable( const il_const_iterator& it, const memory_t& desc )
|
|
: variable( it, descriptor_t{ desc } ) {}
|
|
variable( const il_const_iterator& it, const register_t& desc )
|
|
: variable( it, descriptor_t{ desc } ) {}
|
|
|
|
// Construct free-form with only the descriptor itself.
|
|
//
|
|
variable( descriptor_t desc );
|
|
variable( const memory_t& desc ) : variable( descriptor_t{ desc } ) {}
|
|
variable( const register_t& desc ) : variable( descriptor_t{ desc } ) {}
|
|
|
|
// Returns whether the variable is valid or not.
|
|
//
|
|
bool is_valid( bool force = false ) const;
|
|
|
|
// Swaps the current iterator.
|
|
//
|
|
variable& bind( il_const_iterator it ) { at = std::move( it ); return *this; }
|
|
|
|
// Returns whether it is bound to a free-form iterator or not.
|
|
//
|
|
bool is_free_form() const;
|
|
|
|
// Wrappers around std::hold_alternative for convinient type checks.
|
|
//
|
|
bool is_memory() const { return std::holds_alternative<memory_t>( descriptor ); }
|
|
bool is_register() const { return std::holds_alternative<register_t>( descriptor ); }
|
|
|
|
// Wrappers around std::get.
|
|
//
|
|
memory_t& mem() { return std::get<memory_t>( descriptor ); }
|
|
const memory_t& mem() const { return std::get<memory_t>( descriptor ); }
|
|
register_t& reg() { return std::get<register_t>( descriptor ); }
|
|
const register_t& reg() const { return std::get<register_t>( descriptor ); }
|
|
|
|
// Returns the size of the variable in bits.
|
|
//
|
|
bitcnt_t bit_count() const { return std::visit( [ ] ( auto&& desc ) { return desc.bit_count; }, descriptor ); }
|
|
|
|
// Conversion to symbolic expression.
|
|
//
|
|
expression to_expression( bool unpack = true ) const;
|
|
|
|
// Conversion to human-readable format.
|
|
//
|
|
std::string to_string() const;
|
|
|
|
// Declare reduction.
|
|
//
|
|
REDUCE_TO( at.block, at.entry, descriptor, is_branch_dependant );
|
|
|
|
// Packs all the variables in the expression where it'd be optimal.
|
|
//
|
|
static expression::reference& pack_all( expression::reference& exp );
|
|
[[nodiscard]] static expression::reference pack_all( const expression::reference& exp );
|
|
[[nodiscard]] static expression pack_all( const expression& exp ) { return *pack_all( make_local_reference( &exp ) ); }
|
|
|
|
// Checks if the variable is read by / written by / accessed 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 read_by( const il_const_iterator& it, tracer* tr = nullptr, bool xblock = false ) const;
|
|
access_details written_by( const il_const_iterator& it, tracer* tr = nullptr, bool xblock = false ) const;
|
|
access_details accessed_by( const il_const_iterator& it, tracer* tr = nullptr, bool xblock = false ) const;
|
|
};
|
|
|
|
// Implement lazy wrappers for symbolic variable creation.
|
|
//
|
|
namespace impl
|
|
{
|
|
template<typename iterator_type>
|
|
struct bound_memory
|
|
{
|
|
// Self referential temporary used to implictly declare size upon operator[] invokation.
|
|
//
|
|
struct size_proxy
|
|
{
|
|
const iterator_type& it;
|
|
bitcnt_t n;
|
|
|
|
// operator[](P) reads the given pointer according to the predetermined size.
|
|
//
|
|
auto operator[]( const pointer& p ) const { return variable( it, { p, n } ).to_expression(); }
|
|
};
|
|
|
|
// Iterator data is read at.
|
|
//
|
|
iterator_type it;
|
|
|
|
// Default sizes memory can be accessed with..
|
|
//
|
|
const size_proxy qword = { it, 64 };
|
|
const size_proxy dword = { it, 32 };
|
|
const size_proxy word = { it, 16 };
|
|
const size_proxy byte = { it, 8 };
|
|
|
|
// Iterator copying constructor.
|
|
//
|
|
constexpr bound_memory( iterator_type _it ) : it{ _it } {}
|
|
|
|
// Operator()(P, N) is used to read arbitrary size at [P].
|
|
//
|
|
auto operator()( const pointer& p, bitcnt_t n ) const { return size_proxy{ it, n }[ p ]; }
|
|
};
|
|
|
|
// Declare global wrapper, by default binding to free form iterator.
|
|
//
|
|
struct memory_wrapper : bound_memory<const il_const_iterator&>
|
|
{
|
|
// Inherit operator() for arbitrary size read.
|
|
//
|
|
using bound_memory::operator();
|
|
|
|
// Default construction.
|
|
//
|
|
constexpr memory_wrapper() : bound_memory{ free_form_iterator } {}
|
|
|
|
// operator()( T iterator ) binds the memory state to the given iterator.
|
|
//
|
|
template<typename T = il_const_iterator>
|
|
auto operator()( T&& it ) const { return bound_memory<T>{ std::forward<T>( it ) }; }
|
|
};
|
|
|
|
template<typename iterator_type>
|
|
struct bound_context
|
|
{
|
|
// Iterator context is read at.
|
|
//
|
|
iterator_type it;
|
|
|
|
// Iterator copying constructor.
|
|
//
|
|
constexpr bound_context( iterator_type _it ) : it{ _it } {}
|
|
|
|
// operator[](R) reads the given register at the current iterator position.
|
|
//
|
|
template<typename T>
|
|
auto operator[]( T&& id ) const { return variable( it, register_cast<std::decay_t<T>>{}( std::forward<T>( id ) ) ).to_expression(); }
|
|
};
|
|
|
|
struct context_wrapper : bound_context<const il_const_iterator&>
|
|
{
|
|
// Inherit operator[] for generic read.
|
|
//
|
|
using bound_context::operator[];
|
|
|
|
// Default construction.
|
|
//
|
|
constexpr context_wrapper() : bound_context{ free_form_iterator } {}
|
|
|
|
// operator()( T iterator ) binds the context state to the given iterator.
|
|
//
|
|
template<typename T = il_const_iterator>
|
|
auto operator()( T&& it ) const { return bound_context<T>{ std::forward<T>( it ) }; }
|
|
};
|
|
};
|
|
|
|
static constexpr impl::memory_wrapper MEMORY = {};
|
|
static constexpr impl::context_wrapper CTX = {};
|
|
}; |