mirror of
https://github.com/vtil-project/VTIL-Core
synced 2026-08-17 08:23:03 -04:00
unique_ptr ownership, safer virtual interfaces, and pch/unity speedups and more
This commit is contained in:
parent
88d01439dd
commit
667eea2b1e
27 changed files with 257 additions and 129 deletions
|
|
@ -1,5 +1,5 @@
|
|||
# Require at least CMake version 3.14.5 or later for FetchContent
|
||||
cmake_minimum_required(VERSION 3.14.5)
|
||||
# Require at least CMake version 3.16 for FetchContent and target_precompiled_headers
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# Define the VTIL project
|
||||
project(VTIL-Core)
|
||||
|
|
@ -14,6 +14,8 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
|||
endif()
|
||||
|
||||
option(VTIL_BUILD_TESTS "Build tests" ${VTIL_ROOT_PROJECT})
|
||||
option(VTIL_UNITY_BUILD "Enable unity build for faster compilation" OFF)
|
||||
option(VTIL_SANITIZE_THREADS "Enable ThreadSanitizer (Clang/GCC only)" OFF)
|
||||
|
||||
# Load the dependencies
|
||||
set(CMAKE_FOLDER "VTIL-Core/Dependencies")
|
||||
|
|
|
|||
|
|
@ -14,3 +14,9 @@ source_group(TREE ${PROJECT_SOURCE_DIR} FILES ${SOURCES} ${INCLUDES})
|
|||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC includes)
|
||||
target_link_libraries(${PROJECT_NAME} VTIL-Common VTIL-SymEx)
|
||||
|
||||
target_precompile_headers(${PROJECT_NAME} REUSE_FROM VTIL-Common)
|
||||
|
||||
if(VTIL_UNITY_BUILD)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD ON)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ namespace vtil
|
|||
//
|
||||
if ( owner )
|
||||
for ( auto& [vip, blk] : owner->explored_blocks )
|
||||
fassert( blk != this );
|
||||
fassert( blk.get() != this );
|
||||
|
||||
// Destroy instruction list.
|
||||
//
|
||||
|
|
|
|||
14
VTIL-Architecture/routine/fwd.hpp
Normal file
14
VTIL-Architecture/routine/fwd.hpp
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
// Forward declarations for routine types to reduce header dependencies.
|
||||
//
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace vtil
|
||||
{
|
||||
struct basic_block;
|
||||
struct routine;
|
||||
struct instruction;
|
||||
using vip_t = uint64_t;
|
||||
};
|
||||
|
|
@ -155,7 +155,7 @@ namespace vtil
|
|||
|
||||
auto it = explored_blocks.find( vip );
|
||||
if ( it == explored_blocks.end() ) return nullptr;
|
||||
return it->second;
|
||||
return it->second.get();
|
||||
}
|
||||
basic_block* routine::get_block( vip_t vip ) const
|
||||
{
|
||||
|
|
@ -180,25 +180,26 @@ namespace vtil
|
|||
// Try inserting into the map:
|
||||
//
|
||||
auto [it, inserted] = explored_blocks.emplace( vip, nullptr );
|
||||
basic_block*& block = it->second;
|
||||
if ( inserted )
|
||||
{
|
||||
// Create the block and set entry if none set.
|
||||
//
|
||||
block = new basic_block( this, vip );
|
||||
if ( !entry_point ) entry_point = block;
|
||||
|
||||
it->second = std::make_unique<basic_block>( this, vip );
|
||||
if ( !entry_point ) entry_point = it->second.get();
|
||||
|
||||
// Create self link.
|
||||
//
|
||||
path_cache[ block ][ block ].insert( block );
|
||||
path_cache[ it->second.get() ][ it->second.get() ].insert( it->second.get() );
|
||||
}
|
||||
|
||||
basic_block* block = it->second.get();
|
||||
|
||||
// Fix links and explore the path.
|
||||
//
|
||||
if ( src )
|
||||
{
|
||||
fassert( src->owner == this );
|
||||
|
||||
|
||||
bool new_next = std::find( src->next.begin(), src->next.end(), block ) == src->next.end();
|
||||
bool new_prev = inserted || std::find( block->prev.begin(), block->prev.end(), src ) == block->prev.end();
|
||||
|
||||
|
|
@ -261,10 +262,9 @@ namespace vtil
|
|||
it++;
|
||||
}
|
||||
|
||||
// Remove from explored blocks and delete it.
|
||||
// Remove from explored blocks (unique_ptr automatically deletes the block).
|
||||
//
|
||||
explored_blocks.erase( block->entry_vip );
|
||||
delete block;
|
||||
}
|
||||
|
||||
// Gets a list of exits.
|
||||
|
|
@ -280,7 +280,7 @@ namespace vtil
|
|||
std::vector<const basic_block*> exits;
|
||||
for ( auto& [vip, block] : explored_blocks )
|
||||
if ( block->next.empty() )
|
||||
exits.push_back( block );
|
||||
exits.push_back( block.get() );
|
||||
return exits;
|
||||
}
|
||||
|
||||
|
|
@ -429,8 +429,8 @@ namespace vtil
|
|||
// Sum up instructions in every block.
|
||||
//
|
||||
size_t n = 0;
|
||||
for ( auto& [_, blk] : explored_blocks )
|
||||
n += blk->size();
|
||||
for ( auto& [_, blk_ptr] : explored_blocks )
|
||||
n += blk_ptr->size();
|
||||
return n;
|
||||
}
|
||||
size_t routine::num_branches() const
|
||||
|
|
@ -442,8 +442,8 @@ namespace vtil
|
|||
// Sum up paths in every block.
|
||||
//
|
||||
size_t n = 0;
|
||||
for ( auto& [_, blk] : explored_blocks )
|
||||
n += blk->next.size();
|
||||
for ( auto& [_, blk_ptr] : explored_blocks )
|
||||
n += blk_ptr->next.size();
|
||||
return n;
|
||||
}
|
||||
|
||||
|
|
@ -455,35 +455,39 @@ namespace vtil
|
|||
//
|
||||
std::lock_guard g{ this->mutex };
|
||||
|
||||
// Clear inter-block references before unique_ptrs auto-destruct,
|
||||
// to avoid dangling pointers during destruction order.
|
||||
//
|
||||
for ( auto& [vip, block] : explored_blocks )
|
||||
{
|
||||
block->next.clear();
|
||||
block->prev.clear();
|
||||
delete std::exchange( block, nullptr );
|
||||
}
|
||||
explored_blocks.clear();
|
||||
}
|
||||
|
||||
// Clones the routine and it's every block.
|
||||
//
|
||||
routine* routine::clone() const
|
||||
std::unique_ptr<routine> routine::clone() const
|
||||
{
|
||||
// Acquire the routine mutex.
|
||||
//
|
||||
std::lock_guard g{ this->mutex };
|
||||
|
||||
// Copy the routine.
|
||||
// Copy the routine (protected copy ctor -- explored_blocks is left empty).
|
||||
//
|
||||
auto copy = new routine( *this );
|
||||
|
||||
// Clone each block referenced.
|
||||
auto copy = std::unique_ptr<routine>( new routine( *this ) );
|
||||
|
||||
// Deep-copy each block from the source routine.
|
||||
//
|
||||
for ( auto& [vip, block] : copy->explored_blocks )
|
||||
for ( auto& [vip, block] : this->explored_blocks )
|
||||
{
|
||||
block = new basic_block( *block );
|
||||
block->owner = copy;
|
||||
auto cloned = std::make_unique<basic_block>( *block );
|
||||
cloned->owner = copy.get();
|
||||
copy->explored_blocks.emplace( vip, std::move( cloned ) );
|
||||
}
|
||||
|
||||
// Fix block links.
|
||||
|
||||
// Fix block links (prev/next still point to source blocks, remap to copy's blocks).
|
||||
//
|
||||
for ( auto& [vip, block] : copy->explored_blocks )
|
||||
for ( auto& list : { &block->next, &block->prev } )
|
||||
|
|
|
|||
|
|
@ -60,9 +60,25 @@ namespace vtil
|
|||
{
|
||||
protected:
|
||||
// This structure cannot be copied without a call to ::clone().
|
||||
// Copy constructor must be explicit since explored_blocks contains unique_ptrs.
|
||||
//
|
||||
routine( const routine& ) = default;
|
||||
routine& operator=( const routine& ) = default;
|
||||
routine( const routine& o )
|
||||
: mutex()
|
||||
, arch_id( o.arch_id )
|
||||
, explored_blocks() // NOT copied -- clone() handles block deep-copy
|
||||
, path_cache() // NOT copied -- clone() rebuilds
|
||||
, entry_point( nullptr )
|
||||
, last_internal_id( o.last_internal_id.load() )
|
||||
, routine_convention( o.routine_convention )
|
||||
, subroutine_convention( o.subroutine_convention )
|
||||
, spec_subroutine_conventions( o.spec_subroutine_conventions )
|
||||
, local_opt_count( o.local_opt_count.load() )
|
||||
, context( o.context )
|
||||
, depth_ordered_list_cache{}
|
||||
, cfg_epoch( o.cfg_epoch )
|
||||
, epoch( o.epoch.load() )
|
||||
{}
|
||||
routine& operator=( const routine& ) = delete;
|
||||
public:
|
||||
// Mutex guarding the whole structure, more information on thread-safety can be found at basic_block.hpp.
|
||||
//
|
||||
|
|
@ -73,8 +89,9 @@ namespace vtil
|
|||
architecture_identifier arch_id;
|
||||
|
||||
// Cache of explored blocks, mapping virtual instruction pointer to the basic block structure.
|
||||
// Blocks are owned by the routine via unique_ptr for automatic lifetime management.
|
||||
//
|
||||
std::unordered_map<vip_t, basic_block*> explored_blocks;
|
||||
std::unordered_map<vip_t, std::unique_ptr<basic_block>> explored_blocks;
|
||||
|
||||
// Cache of paths from block A to block B.
|
||||
//
|
||||
|
|
@ -183,7 +200,7 @@ namespace vtil
|
|||
{
|
||||
std::lock_guard _g( mutex );
|
||||
for ( auto& [vip, block] : explored_blocks )
|
||||
if ( enumerator::invoke( fn, block ).should_break )
|
||||
if ( enumerator::invoke( fn, block.get() ).should_break )
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -268,6 +285,6 @@ namespace vtil
|
|||
|
||||
// Clones the routine and it's every block.
|
||||
//
|
||||
routine* clone() const;
|
||||
std::unique_ptr<routine> clone() const;
|
||||
};
|
||||
};
|
||||
|
|
@ -92,7 +92,8 @@ namespace vtil
|
|||
//
|
||||
vip_t vip;
|
||||
deserialize( in, vip );
|
||||
blk = new basic_block( rtn, vip );
|
||||
auto new_block = std::make_unique<basic_block>( rtn, vip );
|
||||
blk = new_block.get();
|
||||
deserialize( in, blk->sp_offset );
|
||||
deserialize( in, blk->sp_index );
|
||||
deserialize( in, blk->last_temporary_index );
|
||||
|
|
@ -100,7 +101,7 @@ namespace vtil
|
|||
deserialize( in, list );
|
||||
blk->assign( list.begin(), list.end() );
|
||||
blk->owner = rtn;
|
||||
blk->owner->explored_blocks[ blk->entry_vip ] = blk;
|
||||
rtn->explored_blocks[ blk->entry_vip ] = std::move( new_block );
|
||||
|
||||
// Read referenced VIP's.
|
||||
//
|
||||
|
|
@ -115,17 +116,17 @@ namespace vtil
|
|||
{
|
||||
// Reference the cached instance.
|
||||
//
|
||||
basic_block*& blk = rtn->explored_blocks[ vip ];
|
||||
auto& blk_ptr = rtn->explored_blocks[ vip ];
|
||||
|
||||
// Keep reading next block until referenced block is found,
|
||||
// once it is found break out of the loop and return the block.
|
||||
//
|
||||
while ( !blk )
|
||||
while ( !blk_ptr )
|
||||
{
|
||||
basic_block* tmp;
|
||||
deserialize( in, rtn, tmp );
|
||||
}
|
||||
return blk;
|
||||
return blk_ptr.get();
|
||||
};
|
||||
std::transform( prev.begin(), prev.end(), std::back_inserter( blk->prev ), ref_resolve );
|
||||
std::transform( next.begin(), next.end(), std::back_inserter( blk->next ), ref_resolve );
|
||||
|
|
@ -161,7 +162,7 @@ namespace vtil
|
|||
// Dump all blocks in cached order.
|
||||
//
|
||||
for ( auto& pair : rtn->explored_blocks )
|
||||
serialize( out, pair.second );
|
||||
serialize( out, pair.second.get() );
|
||||
}
|
||||
void deserialize( std::istream& in, routine*& rtn )
|
||||
{
|
||||
|
|
@ -176,7 +177,7 @@ namespace vtil
|
|||
|
||||
// Create a new routine.
|
||||
//
|
||||
rtn = new routine( hdr.arch_id );
|
||||
rtn = new routine( hdr.arch_id ); // Caller takes ownership
|
||||
|
||||
// Read the entry point VIP.
|
||||
//
|
||||
|
|
@ -210,7 +211,7 @@ namespace vtil
|
|||
|
||||
// Assign the fetched entry point from cache and return.
|
||||
//
|
||||
rtn->entry_point = rtn->explored_blocks[ entry_vip ];
|
||||
rtn->entry_point = rtn->explored_blocks[ entry_vip ].get();
|
||||
if ( !rtn->entry_point )
|
||||
throw std::runtime_error( "Failed resolving entry point." );
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,10 @@ namespace vtil
|
|||
//
|
||||
struct tracer
|
||||
{
|
||||
// Virtual destructor for safe polymorphic deletion.
|
||||
//
|
||||
virtual ~tracer() = default;
|
||||
|
||||
inline static thread_local bool recursive_flag = false;
|
||||
|
||||
// Traces a variable across the basic block it belongs to and generates a symbolic expression
|
||||
|
|
|
|||
|
|
@ -48,19 +48,25 @@ namespace vtil
|
|||
{
|
||||
// Reads from the register.
|
||||
//
|
||||
virtual symbolic::expression::reference read_register( const register_desc& desc ) const { unreachable(); return {}; }
|
||||
|
||||
// Virtual destructor for safe polymorphic deletion.
|
||||
//
|
||||
virtual ~vm_interface() = default;
|
||||
|
||||
// Reads from the register.
|
||||
//
|
||||
virtual symbolic::expression::reference read_register( const register_desc& desc ) const { logger::error( "Pure virtual call to vm_interface::read_register" ); }
|
||||
|
||||
// Reads the given number of bytes from the memory, returns null if aliasing fails.
|
||||
//
|
||||
virtual symbolic::expression::reference read_memory( const symbolic::expression::reference& pointer, size_t byte_count ) const { unreachable(); return {}; }
|
||||
virtual symbolic::expression::reference read_memory( const symbolic::expression::reference& pointer, size_t byte_count ) const { logger::error( "Pure virtual call to vm_interface::read_memory" ); }
|
||||
|
||||
// Writes to the register.
|
||||
//
|
||||
virtual void write_register( const register_desc& desc,symbolic::expression::reference value ) { unreachable(); }
|
||||
|
||||
virtual void write_register( const register_desc& desc,symbolic::expression::reference value ) { logger::error( "Pure virtual call to vm_interface::write_register" ); }
|
||||
|
||||
// Writes the given expression to the memory, returns false if aliasing fails.
|
||||
//
|
||||
virtual bool write_memory( const symbolic::expression::reference& pointer, deferred_value<symbolic::expression::reference> value, bitcnt_t size ) { unreachable(); return false; }
|
||||
virtual bool write_memory( const symbolic::expression::reference& pointer, deferred_value<symbolic::expression::reference> value, bitcnt_t size ) { logger::error( "Pure virtual call to vm_interface::write_memory" ); }
|
||||
bool write_memory_v( const symbolic::expression::reference& pointer, symbolic::expression::reference value ) { return write_memory( pointer, std::move( value ), value.size() ); }
|
||||
|
||||
// Runs the given instruction, returns whether it was successful.
|
||||
|
|
|
|||
|
|
@ -58,4 +58,32 @@ endif()
|
|||
|
||||
# Include Threads
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT})
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT})
|
||||
|
||||
target_precompile_headers(${PROJECT_NAME} PUBLIC
|
||||
<atomic>
|
||||
<mutex>
|
||||
<string>
|
||||
<vector>
|
||||
<unordered_map>
|
||||
<unordered_set>
|
||||
<type_traits>
|
||||
<functional>
|
||||
<optional>
|
||||
<cstdint>
|
||||
<memory>
|
||||
<algorithm>
|
||||
<tuple>
|
||||
<array>
|
||||
<chrono>
|
||||
<string_view>
|
||||
)
|
||||
|
||||
if(VTIL_UNITY_BUILD)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD ON)
|
||||
endif()
|
||||
|
||||
if(VTIL_SANITIZE_THREADS AND CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC -fsanitize=thread)
|
||||
target_link_options(${PROJECT_NAME} PUBLIC -fsanitize=thread)
|
||||
endif()
|
||||
|
|
@ -71,11 +71,16 @@ namespace vtil
|
|||
// Declare debug assertions, dassert is only asserted in debug mode, dassert_s
|
||||
// has the same functionality but is still evaluated in release mode.
|
||||
//
|
||||
// Release-mode assertion: always active regardless of build configuration.
|
||||
// Use for critical invariants that must never be violated (e.g., null checks after allocation).
|
||||
//
|
||||
#define release_assert(...) vtil::abort_if(!bool(__VA_ARGS__), fassert__stringify(__VA_ARGS__) " at " __FILE__ ":" fassert__istringify(__LINE__) )
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define dassert(...) fassert( __VA_ARGS__ )
|
||||
#define dassert_s( ... ) fassert( __VA_ARGS__ )
|
||||
#else
|
||||
#define dassert(...)
|
||||
#define dassert(...)
|
||||
#define dassert_s( ... ) ( __VA_ARGS__ )
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -252,16 +252,17 @@ namespace vtil::logger
|
|||
format::fix_parameter<params>( std::forward<params>( ps ) )...
|
||||
);
|
||||
|
||||
// Try acquiring the lock.
|
||||
// Try acquiring the lock. Intentionally prints even without lock
|
||||
// for crash safety -- warnings during deadlocks must still be visible.
|
||||
//
|
||||
bool locked = logger_state.try_lock( 100ms );
|
||||
|
||||
|
||||
// Print the warning.
|
||||
//
|
||||
set_color( CON_YLW );
|
||||
fprintf( VTIL_LOGGER_ERR_DST, "\n[!] Warning: %s\n", message.c_str() );
|
||||
|
||||
// Unlock if previously locked.
|
||||
// Release the lock if acquired.
|
||||
//
|
||||
if ( locked ) logger_state.unlock();
|
||||
}
|
||||
|
|
@ -296,6 +297,15 @@ namespace vtil::logger
|
|||
set_color( CON_RED );
|
||||
fprintf( VTIL_LOGGER_ERR_DST, "\n[*] Error: %s\n", message.c_str() );
|
||||
|
||||
// Print a minimal stack trace for post-mortem traceability.
|
||||
//
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
{
|
||||
fprintf( VTIL_LOGGER_ERR_DST, "[*] Stack trace:\n" );
|
||||
fprintf( VTIL_LOGGER_ERR_DST, " [0] %p\n", __builtin_return_address( 0 ) );
|
||||
}
|
||||
#endif
|
||||
|
||||
// Break the program, leave the logger locked since we'll break anyways.
|
||||
//
|
||||
unreachable();
|
||||
|
|
@ -320,7 +330,11 @@ namespace vtil::logger
|
|||
fprintf( VTIL_LOGGER_ERR_DST, "\n[*] Error: %s\n", message.c_str() );
|
||||
sleep_for( 1000ms );
|
||||
}
|
||||
catch ( ... ) {}
|
||||
catch ( ... )
|
||||
{
|
||||
set_color( CON_RED );
|
||||
fprintf( VTIL_LOGGER_ERR_DST, "\n[*] Error: Unknown non-std exception caught during terminate\n" );
|
||||
}
|
||||
|
||||
// Call into previous handler if relevant.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -67,21 +67,22 @@ namespace vtil
|
|||
}
|
||||
}
|
||||
|
||||
// Legacy SFINAE helper kept for reference; prefer requires clause below.
|
||||
template<typename T, typename... params>
|
||||
using enable_if_constructor = typename std::enable_if_t<should_invoke_constructor<T, params...>(), int>;
|
||||
|
||||
template<typename T>
|
||||
inline static T* reloc_const( const T* ptr, const void* src, void* dst )
|
||||
{
|
||||
int64_t reloc_delta = ( int64_t ) dst - ( int64_t ) src;
|
||||
return ( T* ) ( ( uint64_t ) ptr + reloc_delta );
|
||||
int64_t reloc_delta = reinterpret_cast<intptr_t>( dst ) - reinterpret_cast<intptr_t>( src );
|
||||
return reinterpret_cast<T*>( reinterpret_cast<uintptr_t>( ptr ) + reloc_delta );
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline static T& reloc_const( const T& ref, const void* src, void* dst )
|
||||
{
|
||||
int64_t reloc_delta = ( int64_t ) dst - ( int64_t ) src;
|
||||
return *( T* ) ( ( uint64_t ) &ref + reloc_delta );
|
||||
int64_t reloc_delta = reinterpret_cast<intptr_t>( dst ) - reinterpret_cast<intptr_t>( src );
|
||||
return *reinterpret_cast<T*>( reinterpret_cast<uintptr_t>( &ref ) + reloc_delta );
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -97,29 +98,19 @@ namespace vtil
|
|||
|
||||
// Wrap atomic operations on reference counter.
|
||||
//
|
||||
// Atomic reference counting with consistent memory ordering across all platforms.
|
||||
//
|
||||
__forceinline static void inc_ref( object_entry* entry )
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
std::atomic_fetch_add_explicit( &entry->second, +1, std::memory_order::relaxed );
|
||||
#else
|
||||
entry->second++;
|
||||
#endif
|
||||
}
|
||||
__forceinline static bool dec_ref( object_entry* entry )
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return std::atomic_fetch_add_explicit( &entry->second, -1, std::memory_order::acq_rel ) == 1;
|
||||
#else
|
||||
return --entry->second == 0;
|
||||
#endif
|
||||
}
|
||||
__forceinline static long get_ref( object_entry* entry )
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return std::atomic_load_explicit( &entry->second, std::memory_order::relaxed );
|
||||
#else
|
||||
return entry->second.load();
|
||||
#endif
|
||||
return std::atomic_load_explicit( &entry->second, std::memory_order::acquire );
|
||||
}
|
||||
|
||||
// Store pointer as a 63-bit integer and append an additional bit to control temporary/allocated.
|
||||
|
|
@ -145,7 +136,8 @@ namespace vtil
|
|||
|
||||
// Owning reference constructor.
|
||||
//
|
||||
template<typename... params, impl::enable_if_constructor<shared_reference<T>, params...> = 0>
|
||||
template<typename... params>
|
||||
requires ( impl::should_invoke_constructor<shared_reference<T>, params...>() )
|
||||
shared_reference( params&&... p )
|
||||
{
|
||||
combined_value = ( uint64_t ) object_pool::construct
|
||||
|
|
@ -241,13 +233,13 @@ namespace vtil
|
|||
|
||||
// Gets object itself.
|
||||
//
|
||||
constexpr const T* get() const { return ( const T* ) pointer; }
|
||||
constexpr const T* get() const { return reinterpret_cast<const T*>( static_cast<uintptr_t>( pointer ) ); }
|
||||
|
||||
// Check if temporary pointer.
|
||||
// - Micro optimized to generate cmp branch instead of bitmasked
|
||||
// test since MSVC is too stupid apparently.
|
||||
//
|
||||
constexpr bool is_temporary() const { return ( ( int64_t ) combined_value ) < 0; /*return temporary;*/ }
|
||||
constexpr bool is_temporary() const { return ( static_cast<int64_t>( combined_value ) ) < 0; /*return temporary;*/ }
|
||||
|
||||
// Converts to owning reference.
|
||||
//
|
||||
|
|
@ -271,7 +263,7 @@ namespace vtil
|
|||
|
||||
// Return the current pointer without const-qualifiers.
|
||||
//
|
||||
return ( T* ) combined_value;
|
||||
return reinterpret_cast<T*>( combined_value );
|
||||
}
|
||||
|
||||
// Simple validity checks.
|
||||
|
|
@ -307,7 +299,7 @@ namespace vtil
|
|||
T* operator+()
|
||||
{
|
||||
if ( is_temporary() ) [[unlikely]]
|
||||
return ( T* ) pointer;
|
||||
return reinterpret_cast<T*>( static_cast<uintptr_t>( pointer ) );
|
||||
return own();
|
||||
}
|
||||
|
||||
|
|
@ -377,7 +369,7 @@ namespace vtil
|
|||
|
||||
// Redirect pointer and dereferencing operator to the reference and cast to const-qualified equivalent.
|
||||
//
|
||||
constexpr const T* get() const { return ( const T* ) pointer; }
|
||||
constexpr const T* get() const { return reinterpret_cast<const T*>( static_cast<uintptr_t>( pointer ) ); }
|
||||
constexpr const T* operator->() const { return get(); }
|
||||
constexpr const T& operator*() const { return *get(); }
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@
|
|||
#pragma once
|
||||
#include "type_helpers.hpp"
|
||||
#include "../io/asserts.hpp"
|
||||
#ifdef _DEBUG
|
||||
#include <typeinfo>
|
||||
#endif
|
||||
|
||||
namespace vtil
|
||||
{
|
||||
|
|
@ -44,6 +47,9 @@ namespace vtil
|
|||
void* obj = nullptr;
|
||||
Ret( *fn )( void*, Args... ) = nullptr;
|
||||
bool const_invocable = true;
|
||||
#ifdef _DEBUG
|
||||
const std::type_info* stored_type = nullptr;
|
||||
#endif
|
||||
|
||||
// Null construction.
|
||||
//
|
||||
|
|
@ -61,6 +67,9 @@ namespace vtil
|
|||
return ( *( F* ) obj )( std::forward<Args>( args )... );
|
||||
};
|
||||
const_invocable = Invocable<std::add_const_t<std::decay_t<F>>, Ret, Args...>;
|
||||
#ifdef _DEBUG
|
||||
stored_type = &typeid( F );
|
||||
#endif
|
||||
}
|
||||
|
||||
// Unsafe for storage.
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ namespace vtil
|
|||
{
|
||||
static_assert( alignof( object_entry ) <= 8, "Object aligned over max alignment." );
|
||||
pool_instance* pool = ( pool_instance* ) malloc( sizeof( pool_instance ) + sizeof( object_entry ) * ( n - 1 ) );
|
||||
release_assert( pool != nullptr );
|
||||
pool->object_count = n;
|
||||
return pool;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,10 +58,14 @@ namespace vtil
|
|||
using base_type = T;
|
||||
using base_type::base_type;
|
||||
|
||||
// Allow copy/move construction and assignment, safety is left to the owner.
|
||||
// Allow copy/move construction (creates a NEW unlocked mutex, does not transfer lock state).
|
||||
// This is intentional: used by routine::clone() to create independent mutex for the copy.
|
||||
//
|
||||
relaxed_mutex( relaxed_mutex&& o ) {}
|
||||
relaxed_mutex( const relaxed_mutex& o ) {}
|
||||
|
||||
// Assignment is a no-op (same rationale as copy/move ctors: mutex state is not transferable).
|
||||
//
|
||||
relaxed_mutex& operator=( relaxed_mutex&& o ) { return *this; }
|
||||
relaxed_mutex& operator=( const relaxed_mutex& o ) { return *this; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,3 +14,9 @@ source_group(TREE ${PROJECT_SOURCE_DIR} FILES ${SOURCES} ${INCLUDES})
|
|||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC includes)
|
||||
target_link_libraries(${PROJECT_NAME} VTIL-Common VTIL-SymEx VTIL-Architecture)
|
||||
|
||||
target_precompile_headers(${PROJECT_NAME} REUSE_FROM VTIL-Common)
|
||||
|
||||
if(VTIL_UNITY_BUILD)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD ON)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -153,9 +153,9 @@ namespace vtil::optimizer
|
|||
{
|
||||
// Invoke parallel transformation.
|
||||
//
|
||||
transform_parallel( rtn->explored_blocks, [ & ] ( const std::pair<const vip_t, basic_block*>& pair )
|
||||
transform_parallel( rtn->explored_blocks, [ & ] ( const std::pair<const vip_t, std::unique_ptr<basic_block>>& pair )
|
||||
{
|
||||
worker( pair.second );
|
||||
worker( pair.second.get() );
|
||||
} );
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,10 +48,11 @@ namespace vtil::optimizer
|
|||
//
|
||||
cached_tracer local_tracer = {};
|
||||
auto lbranch_info = aux::analyze_branch( blk, &local_tracer, {} );
|
||||
ctracer.mtx.lock();
|
||||
for ( auto& [k, v] : local_tracer.cache )
|
||||
ctracer.cache[ k ] = v;
|
||||
ctracer.mtx.unlock();
|
||||
{
|
||||
std::lock_guard _g{ ctracer.mtx };
|
||||
for ( auto& [k, v] : local_tracer.cache )
|
||||
ctracer.cache[ k ] = v;
|
||||
}
|
||||
auto branch_info = aux::analyze_branch( blk, &ctracer, { .cross_block = true, .pack = true, .resolve_opaque = true } );
|
||||
|
||||
// If branching to real, assert single next block.
|
||||
|
|
@ -233,7 +234,7 @@ namespace vtil::optimizer
|
|||
|
||||
for ( auto it = rtn->explored_blocks.begin(); it != rtn->explored_blocks.end(); )
|
||||
{
|
||||
if ( it->second->prev.size() == 0 && it->second != rtn->entry_point )
|
||||
if ( it->second->prev.size() == 0 && it->second.get() != rtn->entry_point )
|
||||
{
|
||||
// For each destination:
|
||||
//
|
||||
|
|
@ -241,14 +242,14 @@ namespace vtil::optimizer
|
|||
{
|
||||
// Remove the link.
|
||||
//
|
||||
block->prev.erase( std::remove( block->prev.begin(), block->prev.end(), it->second ), block->prev.end() );
|
||||
|
||||
block->prev.erase( std::remove( block->prev.begin(), block->prev.end(), it->second.get() ), block->prev.end() );
|
||||
|
||||
// If no prev link left, repeat logic.
|
||||
//
|
||||
repeat |= block->prev.empty();
|
||||
}
|
||||
|
||||
// Erase block.
|
||||
// Erase block (unique_ptr auto-deletes).
|
||||
//
|
||||
it = rtn->explored_blocks.erase( it );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ namespace vtil::optimizer::validation
|
|||
deserialize( iss, rtn );
|
||||
return rtn;
|
||||
}();
|
||||
return std::unique_ptr<routine>{ cache->clone() };
|
||||
return cache->clone();
|
||||
}
|
||||
|
||||
bool test1::validate( const routine* rtn ) const
|
||||
|
|
|
|||
|
|
@ -18,4 +18,10 @@ target_link_libraries(${PROJECT_NAME} VTIL-Common)
|
|||
if(MSVC)
|
||||
# /bigobj for reasons
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE /bigobj)
|
||||
endif()
|
||||
|
||||
target_precompile_headers(${PROJECT_NAME} REUSE_FROM VTIL-Common)
|
||||
|
||||
if(VTIL_UNITY_BUILD)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES UNITY_BUILD ON)
|
||||
endif()
|
||||
|
|
@ -87,24 +87,5 @@ namespace vtil::symbolic::directive
|
|||
return true;
|
||||
}
|
||||
|
||||
// Simple copyable unique pointer implementation.
|
||||
//
|
||||
instance::reference::reference( const instance& o ) : ptr( new instance( o ) ) {}
|
||||
instance::reference::reference( instance&& o ) : ptr( new instance( std::move( o ) ) ) {}
|
||||
instance::reference::reference( const reference& o ) : ptr( o ? new instance( *o ) : nullptr ) {}
|
||||
instance::reference::reference( reference&& o ) : ptr( std::exchange( o.ptr, nullptr ) ) {}
|
||||
instance::reference::~reference()
|
||||
{
|
||||
if ( ptr ) delete ptr;
|
||||
}
|
||||
instance::reference& instance::reference::operator=( instance::reference&& o )
|
||||
{
|
||||
ptr = std::exchange( o.ptr, nullptr );
|
||||
return *this;
|
||||
}
|
||||
instance::reference& instance::reference::operator=( const instance::reference& o )
|
||||
{
|
||||
ptr = o ? new instance( *o ) : nullptr;
|
||||
return *this;
|
||||
}
|
||||
// Note: instance::reference is now fully defined inline in directive.hpp :))
|
||||
};
|
||||
|
|
@ -148,41 +148,45 @@ namespace vtil::symbolic::directive
|
|||
//
|
||||
struct instance : math::operable<instance>
|
||||
{
|
||||
// Simple copyable unique pointer implementation.
|
||||
// Deep-copying unique pointer for directive instances.
|
||||
//
|
||||
struct reference
|
||||
{
|
||||
instance* ptr = nullptr;
|
||||
|
||||
std::unique_ptr<instance> ptr;
|
||||
|
||||
// Construct by implicit null or instance value.
|
||||
//
|
||||
reference() {}
|
||||
reference( const instance& i );
|
||||
reference( instance&& i );
|
||||
reference() = default;
|
||||
reference( const instance& i ) : ptr( std::make_unique<instance>( i ) ) {}
|
||||
reference( instance&& i ) : ptr( std::make_unique<instance>( std::move( i ) ) ) {}
|
||||
|
||||
// Copy / Move from another reference.
|
||||
// Deep-copy from another reference, move is default.
|
||||
//
|
||||
reference( const reference& o );
|
||||
reference( reference&& o );
|
||||
reference& operator=( reference&& o );
|
||||
reference& operator=( const reference& o );
|
||||
reference( const reference& o ) : ptr( o.ptr ? std::make_unique<instance>( *o.ptr ) : nullptr ) {}
|
||||
reference( reference&& ) = default;
|
||||
reference& operator=( reference&& ) = default;
|
||||
reference& operator=( const reference& o )
|
||||
{
|
||||
ptr = o.ptr ? std::make_unique<instance>( *o.ptr ) : nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Destructor deletes the value.
|
||||
// Default destructor.
|
||||
//
|
||||
~reference();
|
||||
~reference() = default;
|
||||
|
||||
// Null check.
|
||||
//
|
||||
explicit operator bool() const { return ptr; }
|
||||
|
||||
explicit operator bool() const { return ptr != nullptr; }
|
||||
|
||||
// Pointer interface.
|
||||
//
|
||||
operator instance*() { return ptr; }
|
||||
operator const instance*() const { return ptr; }
|
||||
operator instance*() { return ptr.get(); }
|
||||
operator const instance*() const { return ptr.get(); }
|
||||
instance& operator*() { return *ptr; }
|
||||
const instance& operator*() const { return *ptr; }
|
||||
instance* operator->() { return ptr; }
|
||||
const instance* operator->() const { return ptr; }
|
||||
instance* operator->() { return ptr.get(); }
|
||||
const instance* operator->() const { return ptr.get(); }
|
||||
};
|
||||
|
||||
// If symbolic variable, the identifier of the variable
|
||||
|
|
|
|||
|
|
@ -83,8 +83,8 @@ namespace vtil::symbolic
|
|||
expression::reference lhs, rhs;
|
||||
|
||||
std::array tx = {
|
||||
std::pair( &lhs, dir->lhs.ptr ),
|
||||
std::pair( &rhs, dir->rhs.ptr )
|
||||
std::pair( &lhs, dir->lhs.ptr.get() ),
|
||||
std::pair( &rhs, dir->rhs.ptr.get() )
|
||||
};
|
||||
|
||||
if ( tx[ 1 ].second->priority > tx[ 0 ].second->priority )
|
||||
|
|
|
|||
11
VTIL-SymEx/expressions/fwd.hpp
Normal file
11
VTIL-SymEx/expressions/fwd.hpp
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
// Forward declarations for expression types to reduce header dependencies.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
namespace vtil::symbolic
|
||||
{
|
||||
struct expression;
|
||||
struct unique_identifier;
|
||||
};
|
||||
|
|
@ -1,3 +1,9 @@
|
|||
#if defined(__APPLE__) && (defined(__aarch64__) || defined(__arm64__))
|
||||
#ifndef DOCTEST_BREAK_INTO_DEBUGGER
|
||||
#define DOCTEST_BREAK_INTO_DEBUGGER() __builtin_debugtrap()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "doctest.h"
|
||||
#include <vtil/vtil>
|
||||
#include <vtil/arch>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,11 @@
|
|||
#define DOCTEST_CONFIG_NO_POSIX_SIGNALS
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && (defined(__aarch64__) || defined(__arm64__))
|
||||
#ifndef DOCTEST_BREAK_INTO_DEBUGGER
|
||||
#define DOCTEST_BREAK_INTO_DEBUGGER() __builtin_debugtrap()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include "doctest.h"
|
||||
Loading…
Add table
Add a link
Reference in a new issue