diff --git a/CMakeLists.txt b/CMakeLists.txt index c87b62f..8fc0c4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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") diff --git a/VTIL-Architecture/CMakeLists.txt b/VTIL-Architecture/CMakeLists.txt index 9207f67..d86b087 100644 --- a/VTIL-Architecture/CMakeLists.txt +++ b/VTIL-Architecture/CMakeLists.txt @@ -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() diff --git a/VTIL-Architecture/routine/basic_block.hpp b/VTIL-Architecture/routine/basic_block.hpp index 408b94b..89b805b 100644 --- a/VTIL-Architecture/routine/basic_block.hpp +++ b/VTIL-Architecture/routine/basic_block.hpp @@ -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. // diff --git a/VTIL-Architecture/routine/fwd.hpp b/VTIL-Architecture/routine/fwd.hpp new file mode 100644 index 0000000..da69e5e --- /dev/null +++ b/VTIL-Architecture/routine/fwd.hpp @@ -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 + +namespace vtil +{ + struct basic_block; + struct routine; + struct instruction; + using vip_t = uint64_t; +}; diff --git a/VTIL-Architecture/routine/routine.cpp b/VTIL-Architecture/routine/routine.cpp index f8863f3..921e1e2 100644 --- a/VTIL-Architecture/routine/routine.cpp +++ b/VTIL-Architecture/routine/routine.cpp @@ -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( 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 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::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( 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( *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 } ) diff --git a/VTIL-Architecture/routine/routine.hpp b/VTIL-Architecture/routine/routine.hpp index 12e41c1..d47453b 100644 --- a/VTIL-Architecture/routine/routine.hpp +++ b/VTIL-Architecture/routine/routine.hpp @@ -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 explored_blocks; + std::unordered_map> 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 clone() const; }; }; \ No newline at end of file diff --git a/VTIL-Architecture/routine/serialization.cpp b/VTIL-Architecture/routine/serialization.cpp index deb2d97..1e54804 100644 --- a/VTIL-Architecture/routine/serialization.cpp +++ b/VTIL-Architecture/routine/serialization.cpp @@ -92,7 +92,8 @@ namespace vtil // vip_t vip; deserialize( in, vip ); - blk = new basic_block( rtn, vip ); + auto new_block = std::make_unique( 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." ); diff --git a/VTIL-Architecture/trace/tracer.hpp b/VTIL-Architecture/trace/tracer.hpp index b25514d..0310ee3 100644 --- a/VTIL-Architecture/trace/tracer.hpp +++ b/VTIL-Architecture/trace/tracer.hpp @@ -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 diff --git a/VTIL-Architecture/vm/interface.hpp b/VTIL-Architecture/vm/interface.hpp index efc0cea..7e0c109 100644 --- a/VTIL-Architecture/vm/interface.hpp +++ b/VTIL-Architecture/vm/interface.hpp @@ -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 value, bitcnt_t size ) { unreachable(); return false; } + virtual bool write_memory( const symbolic::expression::reference& pointer, deferred_value 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. diff --git a/VTIL-Common/CMakeLists.txt b/VTIL-Common/CMakeLists.txt index 46840f0..7550315 100644 --- a/VTIL-Common/CMakeLists.txt +++ b/VTIL-Common/CMakeLists.txt @@ -58,4 +58,32 @@ endif() # Include Threads find_package(Threads REQUIRED) -target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT}) \ No newline at end of file +target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT}) + +target_precompile_headers(${PROJECT_NAME} PUBLIC + + + + + + + + + + + + + + + + +) + +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() \ No newline at end of file diff --git a/VTIL-Common/io/asserts.hpp b/VTIL-Common/io/asserts.hpp index fa8334b..a4f0da7 100644 --- a/VTIL-Common/io/asserts.hpp +++ b/VTIL-Common/io/asserts.hpp @@ -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 diff --git a/VTIL-Common/io/logger.hpp b/VTIL-Common/io/logger.hpp index f5966dd..103c50e 100644 --- a/VTIL-Common/io/logger.hpp +++ b/VTIL-Common/io/logger.hpp @@ -252,16 +252,17 @@ namespace vtil::logger format::fix_parameter( std::forward( 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. // diff --git a/VTIL-Common/util/copy_on_write.hpp b/VTIL-Common/util/copy_on_write.hpp index f1e2044..3fb0283 100644 --- a/VTIL-Common/util/copy_on_write.hpp +++ b/VTIL-Common/util/copy_on_write.hpp @@ -67,21 +67,22 @@ namespace vtil } } + // Legacy SFINAE helper kept for reference; prefer requires clause below. template using enable_if_constructor = typename std::enable_if_t(), int>; template 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( dst ) - reinterpret_cast( src ); + return reinterpret_cast( reinterpret_cast( ptr ) + reloc_delta ); } template 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( dst ) - reinterpret_cast( src ); + return *reinterpret_cast( reinterpret_cast( &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, params...> = 0> + template + requires ( impl::should_invoke_constructor, 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( static_cast( 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( 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( combined_value ); } // Simple validity checks. @@ -307,7 +299,7 @@ namespace vtil T* operator+() { if ( is_temporary() ) [[unlikely]] - return ( T* ) pointer; + return reinterpret_cast( static_cast( 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( static_cast( pointer ) ); } constexpr const T* operator->() const { return get(); } constexpr const T& operator*() const { return *get(); } diff --git a/VTIL-Common/util/function_view.hpp b/VTIL-Common/util/function_view.hpp index 14228ad..93300fc 100644 --- a/VTIL-Common/util/function_view.hpp +++ b/VTIL-Common/util/function_view.hpp @@ -28,6 +28,9 @@ #pragma once #include "type_helpers.hpp" #include "../io/asserts.hpp" +#ifdef _DEBUG +#include +#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 )... ); }; const_invocable = Invocable>, Ret, Args...>; +#ifdef _DEBUG + stored_type = &typeid( F ); +#endif } // Unsafe for storage. diff --git a/VTIL-Common/util/object_pool.hpp b/VTIL-Common/util/object_pool.hpp index a1334c8..703b705 100644 --- a/VTIL-Common/util/object_pool.hpp +++ b/VTIL-Common/util/object_pool.hpp @@ -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; } diff --git a/VTIL-Common/util/relaxed_atomics.hpp b/VTIL-Common/util/relaxed_atomics.hpp index 6e6c12d..1461960 100644 --- a/VTIL-Common/util/relaxed_atomics.hpp +++ b/VTIL-Common/util/relaxed_atomics.hpp @@ -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; } }; diff --git a/VTIL-Compiler/CMakeLists.txt b/VTIL-Compiler/CMakeLists.txt index 07abbb3..d6870c1 100644 --- a/VTIL-Compiler/CMakeLists.txt +++ b/VTIL-Compiler/CMakeLists.txt @@ -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() diff --git a/VTIL-Compiler/common/interface.hpp b/VTIL-Compiler/common/interface.hpp index 446540a..86f548e 100644 --- a/VTIL-Compiler/common/interface.hpp +++ b/VTIL-Compiler/common/interface.hpp @@ -153,9 +153,9 @@ namespace vtil::optimizer { // Invoke parallel transformation. // - transform_parallel( rtn->explored_blocks, [ & ] ( const std::pair& pair ) + transform_parallel( rtn->explored_blocks, [ & ] ( const std::pair>& pair ) { - worker( pair.second ); + worker( pair.second.get() ); } ); break; } diff --git a/VTIL-Compiler/optimizer/branch_correction_pass.cpp b/VTIL-Compiler/optimizer/branch_correction_pass.cpp index c999df4..6dae029 100644 --- a/VTIL-Compiler/optimizer/branch_correction_pass.cpp +++ b/VTIL-Compiler/optimizer/branch_correction_pass.cpp @@ -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 ); } diff --git a/VTIL-Compiler/validation/test1.cpp b/VTIL-Compiler/validation/test1.cpp index fa4578b..127ef44 100644 --- a/VTIL-Compiler/validation/test1.cpp +++ b/VTIL-Compiler/validation/test1.cpp @@ -42,7 +42,7 @@ namespace vtil::optimizer::validation deserialize( iss, rtn ); return rtn; }(); - return std::unique_ptr{ cache->clone() }; + return cache->clone(); } bool test1::validate( const routine* rtn ) const diff --git a/VTIL-SymEx/CMakeLists.txt b/VTIL-SymEx/CMakeLists.txt index b5da114..610eac4 100644 --- a/VTIL-SymEx/CMakeLists.txt +++ b/VTIL-SymEx/CMakeLists.txt @@ -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() \ No newline at end of file diff --git a/VTIL-SymEx/directives/directive.cpp b/VTIL-SymEx/directives/directive.cpp index 989c2b7..271427e 100644 --- a/VTIL-SymEx/directives/directive.cpp +++ b/VTIL-SymEx/directives/directive.cpp @@ -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 :)) }; \ No newline at end of file diff --git a/VTIL-SymEx/directives/directive.hpp b/VTIL-SymEx/directives/directive.hpp index e7684a8..f190778 100644 --- a/VTIL-SymEx/directives/directive.hpp +++ b/VTIL-SymEx/directives/directive.hpp @@ -148,41 +148,45 @@ namespace vtil::symbolic::directive // struct instance : math::operable { - // Simple copyable unique pointer implementation. + // Deep-copying unique pointer for directive instances. // struct reference { - instance* ptr = nullptr; - + std::unique_ptr 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( i ) ) {} + reference( instance&& i ) : ptr( std::make_unique( 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( *o.ptr ) : nullptr ) {} + reference( reference&& ) = default; + reference& operator=( reference&& ) = default; + reference& operator=( const reference& o ) + { + ptr = o.ptr ? std::make_unique( *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 diff --git a/VTIL-SymEx/directives/transformer.cpp b/VTIL-SymEx/directives/transformer.cpp index 3fa6dd2..1a9262d 100644 --- a/VTIL-SymEx/directives/transformer.cpp +++ b/VTIL-SymEx/directives/transformer.cpp @@ -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 ) diff --git a/VTIL-SymEx/expressions/fwd.hpp b/VTIL-SymEx/expressions/fwd.hpp new file mode 100644 index 0000000..df0f522 --- /dev/null +++ b/VTIL-SymEx/expressions/fwd.hpp @@ -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; +}; diff --git a/VTIL-Tests/dummy.cpp b/VTIL-Tests/dummy.cpp index f2c0755..4af75ff 100644 --- a/VTIL-Tests/dummy.cpp +++ b/VTIL-Tests/dummy.cpp @@ -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 #include diff --git a/VTIL-Tests/main.cpp b/VTIL-Tests/main.cpp index 1ac4575..69c4f16 100644 --- a/VTIL-Tests/main.cpp +++ b/VTIL-Tests/main.cpp @@ -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" \ No newline at end of file