mirror of
https://github.com/vtil-project/VTIL-Core
synced 2026-08-17 08:23:03 -04:00
Merged with VTIL-Project Common quickly, still WIP.
This commit is contained in:
parent
8b0cbfb0c5
commit
b4921c7784
33 changed files with 1328 additions and 4368 deletions
18
VTIL-Architecture/.gitignore
vendored
Normal file
18
VTIL-Architecture/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
*.db
|
||||
*.ipch
|
||||
*.opendb
|
||||
*.user
|
||||
*.log
|
||||
*.exe
|
||||
*.tlog
|
||||
*.obj
|
||||
*.exp
|
||||
*.pdb
|
||||
*.lib
|
||||
*.suo
|
||||
*.ilk
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
.vs/
|
||||
x64/Debug/
|
||||
x64/Release/
|
||||
28
VTIL-Architecture/VTIL-Core.licenseheader
Normal file
28
VTIL-Architecture/VTIL-Core.licenseheader
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
extensions: .hpp .cpp .h .c
|
||||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
66
VTIL-Architecture/arch/control_registers.cpp
Normal file
66
VTIL-Architecture/arch/control_registers.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#include "control_registers.hpp"
|
||||
|
||||
namespace vtil::arch
|
||||
{
|
||||
// Global list of control registers and the mutex protecting it.
|
||||
//
|
||||
static std::mutex control_register_list_mutex = {};
|
||||
static std::vector<control_register_desc> control_register_list = {};
|
||||
|
||||
// Looks up the descriptor for the given control register.
|
||||
//
|
||||
std::optional<control_register_desc> lookup_control_register( x86_reg reg )
|
||||
{
|
||||
std::lock_guard g( control_register_list_mutex );
|
||||
|
||||
// Calculate the index and lookup the global list
|
||||
//
|
||||
size_t index = reg - X86_REG_VCR0;
|
||||
if ( control_register_list.size() <= index )
|
||||
return std::nullopt;
|
||||
return control_register_list[ index ];
|
||||
}
|
||||
|
||||
// Creates a new control register based on the descriptor and returns the
|
||||
// x86_reg value that it is mapped to.
|
||||
//
|
||||
x86_reg create_control_register( const control_register_desc& descriptor )
|
||||
{
|
||||
std::lock_guard g( control_register_list_mutex );
|
||||
|
||||
// Calculate the index we will place this register at
|
||||
// push it up the list and then return the equivalent
|
||||
// x86_reg value
|
||||
//
|
||||
size_t index = control_register_list.size();
|
||||
control_register_list.push_back( descriptor );
|
||||
return X86_REG_VCR( index );
|
||||
}
|
||||
};
|
||||
|
|
@ -1,7 +1,34 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#pragma once
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <capstone.hpp>
|
||||
#include <vtil/amd64>
|
||||
|
||||
// The x86_reg value that equates to the first user-defined control register
|
||||
// and a handy macro to get the value for Nth instance.
|
||||
|
|
@ -24,38 +51,12 @@ namespace vtil::arch
|
|||
bool read_only = false;
|
||||
};
|
||||
|
||||
// Global list of control registers and the mutex protecting it.
|
||||
//
|
||||
static std::mutex control_register_list_mutex;
|
||||
static std::vector<control_register_desc> control_register_list;
|
||||
|
||||
// Looks up the descriptor for the given control register.
|
||||
//
|
||||
static std::optional<control_register_desc> lookup_control_register( x86_reg reg )
|
||||
{
|
||||
std::lock_guard g( control_register_list_mutex );
|
||||
|
||||
// Calculate the index and lookup the global list
|
||||
//
|
||||
size_t index = reg - X86_REG_VCR0;
|
||||
if ( control_register_list.size() <= index )
|
||||
return std::nullopt;
|
||||
return control_register_list[ index ];
|
||||
}
|
||||
std::optional<control_register_desc> lookup_control_register( x86_reg reg );
|
||||
|
||||
// Creates a new control register based on the descriptor and returns the
|
||||
// x86_reg value that it is mapped to.
|
||||
//
|
||||
static x86_reg create_control_register( const control_register_desc& descriptor )
|
||||
{
|
||||
std::lock_guard g( control_register_list_mutex );
|
||||
|
||||
// Calculate the index we will place this register at
|
||||
// push it up the list and then return the equivalent
|
||||
// x86_reg value
|
||||
//
|
||||
size_t index = control_register_list.size();
|
||||
control_register_list.push_back( descriptor );
|
||||
return X86_REG_VCR( index );
|
||||
}
|
||||
x86_reg create_control_register( const control_register_desc& descriptor );
|
||||
};
|
||||
64
VTIL-Architecture/arch/instruction_desc.cpp
Normal file
64
VTIL-Architecture/arch/instruction_desc.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#include "instruction_desc.hpp"
|
||||
|
||||
namespace vtil::arch
|
||||
{
|
||||
// Generic data-assignment constructor with certain validity checks.
|
||||
//
|
||||
instruction_desc::instruction_desc( const std::string& name,
|
||||
const std::vector<operand_access>& access_types,
|
||||
int access_size_index,
|
||||
bool is_volatile,
|
||||
const std::string& symbolic_operator,
|
||||
std::vector<int> branch_operands,
|
||||
const std::pair<int, bool>& memory_operands ) :
|
||||
name( name ), access_types( access_types ), access_size_index( access_size_index - 1 ),
|
||||
is_volatile( is_volatile ), symbolic_operator( symbolic_operator ),
|
||||
memory_operand_index( memory_operands.first - 1 ), memory_write( memory_operands.second )
|
||||
{
|
||||
fassert( operand_count() <= max_operand_count );
|
||||
|
||||
// Validate all operand indices.
|
||||
//
|
||||
fassert( access_size_index == 0 || abs( access_size_index ) <= operand_count() );
|
||||
fassert( memory_operands.first == 0 || abs( memory_operands.first ) <= operand_count() );
|
||||
for ( int op : branch_operands )
|
||||
fassert( op != 0 && abs( op ) <= operand_count() );
|
||||
|
||||
// Process branch operands.
|
||||
//
|
||||
for ( int op : branch_operands )
|
||||
{
|
||||
if ( op > 0 )
|
||||
branch_operands_vip.push_back( op - 1 );
|
||||
else
|
||||
branch_operands_rip.push_back( -op - 1 );
|
||||
}
|
||||
}
|
||||
};
|
||||
144
VTIL-Architecture/arch/instruction_desc.hpp
Normal file
144
VTIL-Architecture/arch/instruction_desc.hpp
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "operands.hpp"
|
||||
|
||||
namespace vtil::arch
|
||||
{
|
||||
// Maximum operand count.
|
||||
//
|
||||
static constexpr size_t max_operand_count = 4;
|
||||
|
||||
// Describes the way an instruction acceses it's operands and the
|
||||
// constraints built around that, such as "immediate only" implied
|
||||
// by the "_imm" suffix.
|
||||
//
|
||||
enum operand_access : uint8_t
|
||||
{
|
||||
// Note:
|
||||
// It still is valid to do != write for read and >= write for writes.
|
||||
// this operand access type is illegal to use outside of function arguments.
|
||||
//
|
||||
invalid = 0,
|
||||
|
||||
// Read group:
|
||||
//
|
||||
read_imm,
|
||||
read_reg,
|
||||
read_any,
|
||||
read = read_any,
|
||||
|
||||
// Write group:
|
||||
// - Implicit "_reg" as we cannot write into an immediate
|
||||
//
|
||||
write,
|
||||
readwrite
|
||||
};
|
||||
|
||||
// Instruction descriptors are used to describe each unique instruction
|
||||
// in the VTIL instruction set. This type should be only constructed
|
||||
// as a global constant. For the sake of consistency all operand indices
|
||||
// passed to the constructor start from 1. [Ref: branch_operands desc.]
|
||||
//
|
||||
struct instruction_desc
|
||||
{
|
||||
// Name of the instruction.
|
||||
//
|
||||
std::string name;
|
||||
|
||||
// List of the access types for each operand.
|
||||
//
|
||||
std::vector<operand_access> access_types;
|
||||
|
||||
// Index of the operand that determines the instruction's
|
||||
// access size property.
|
||||
//
|
||||
int access_size_index = 0;
|
||||
|
||||
// Whether the instruction is volatile or not meaning it
|
||||
// should not be discarded even if it is no-op or dead.
|
||||
//
|
||||
bool is_volatile = false;
|
||||
|
||||
// A pointer to the expression operator that describes the
|
||||
// operation of this instruction if applicable.
|
||||
//
|
||||
std::string symbolic_operator = "";
|
||||
|
||||
// List of operands that are thread as branching destinations.
|
||||
// - In the constructor version negative indices are used to
|
||||
// indicate "real" destinations and thus for the sake of
|
||||
// simplicity indices start from 1.
|
||||
//
|
||||
std::vector<int> branch_operands_rip = {};
|
||||
std::vector<int> branch_operands_vip = {};
|
||||
|
||||
// Operand that marks the beginning of a memory reference and whether
|
||||
// it writes to the pointer or not. [Idx] must be a register and [Idx+1]
|
||||
// must be an immediate.
|
||||
//
|
||||
int memory_operand_index = -1;
|
||||
bool memory_write = false;
|
||||
|
||||
// Generic data-assignment constructor with certain validity checks.
|
||||
//
|
||||
instruction_desc( const std::string& name,
|
||||
const std::vector<operand_access>& access_types,
|
||||
int access_size_index,
|
||||
bool is_volatile,
|
||||
const std::string& symbolic_operator,
|
||||
std::vector<int> branch_operands,
|
||||
const std::pair<int, bool>& memory_operands );
|
||||
|
||||
// Number of operands this instruction has.
|
||||
//
|
||||
int operand_count() const { return access_types.size(); }
|
||||
|
||||
// Whether the instruction branches for not.
|
||||
//
|
||||
bool is_branching_virt() const { return !branch_operands_vip.empty(); }
|
||||
bool is_branching_real() const { return !branch_operands_rip.empty(); }
|
||||
bool is_branching() const { return is_branching_virt() || is_branching_real(); }
|
||||
|
||||
// Whether the instruction acceses/reads/writes memory or not.
|
||||
//
|
||||
bool reads_memory() const { return accesses_memory() && !memory_write; }
|
||||
bool writes_memory() const { return accesses_memory() && memory_write; }
|
||||
bool accesses_memory() const { return memory_operand_index != -1; }
|
||||
|
||||
// Conversion to human-readable format.
|
||||
//
|
||||
std::string to_string( uint8_t access_size ) const
|
||||
{
|
||||
if ( !access_size ) return name;
|
||||
return name + ( char ) format::suffix_map[ access_size ];
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -1,154 +1,36 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <platform.hpp>
|
||||
#include "operands.hpp"
|
||||
#include "..\misc\format.hpp"
|
||||
#include "..\symbolic\operators.hpp"
|
||||
#include "instruction_desc.hpp"
|
||||
|
||||
namespace vtil::arch
|
||||
{
|
||||
// Maximum operand count.
|
||||
//
|
||||
static constexpr size_t max_operand_count = 4;
|
||||
|
||||
// Describes the way an instruction acceses it's operands and the
|
||||
// constraints built around that, such as "immediate only" implied
|
||||
// by the "_imm" suffix.
|
||||
//
|
||||
enum operand_access : uint8_t
|
||||
{
|
||||
// Note:
|
||||
// It still is valid to do != write for read and >= write for writes.
|
||||
// this operand access type is illegal to use outside of function arguments.
|
||||
//
|
||||
invalid = 0,
|
||||
|
||||
// Read group:
|
||||
//
|
||||
read_imm,
|
||||
read_reg,
|
||||
read_any,
|
||||
read = read_any,
|
||||
|
||||
// Write group:
|
||||
// - Implicit "_reg" as we cannot write into an immediate
|
||||
//
|
||||
write,
|
||||
readwrite
|
||||
};
|
||||
|
||||
// Instruction descriptors are used to describe each unique instruction
|
||||
// in the VTIL instruction set. This type should be only constructed
|
||||
// as a global constant. For the sake of consistency all operand indices
|
||||
// passed to the constructor start from 1. [Ref: branch_operands desc.]
|
||||
//
|
||||
struct instruction_desc
|
||||
{
|
||||
// List of all instances, to be filled by the constructor.
|
||||
//
|
||||
static std::vector<const instruction_desc*> list;
|
||||
|
||||
// Name of the instruction.
|
||||
//
|
||||
std::string name;
|
||||
|
||||
// List of the access types for each operand.
|
||||
//
|
||||
std::vector<operand_access> access_types;
|
||||
|
||||
// Index of the operand that determines the instruction's
|
||||
// access size property.
|
||||
//
|
||||
int access_size_index = 0;
|
||||
|
||||
// Whether the instruction is volatile or not meaning it
|
||||
// should not be discarded even if it is no-op or dead.
|
||||
//
|
||||
bool is_volatile = false;
|
||||
|
||||
// A pointer to the expression operator that describes the
|
||||
// operation of this instruction if applicable.
|
||||
//
|
||||
std::string symbolic_operator = "";
|
||||
|
||||
// List of operands that are thread as branching destinations.
|
||||
// - In the constructor version negative indices are used to
|
||||
// indicate "real" destinations and thus for the sake of
|
||||
// simplicity indices start from 1.
|
||||
//
|
||||
std::vector<int> branch_operands_rip = {};
|
||||
std::vector<int> branch_operands_vip = {};
|
||||
|
||||
// Operand that marks the beginning of a memory reference and whether
|
||||
// it writes to the pointer or not. [Idx] must be a register and [Idx+1]
|
||||
// must be an immediate.
|
||||
//
|
||||
int memory_operand_index = -1;
|
||||
bool memory_write = false;
|
||||
|
||||
// Constructor of this structure will push a reference to
|
||||
// itself up the global ::list, which implies any construction
|
||||
// should take place in a global context.
|
||||
//
|
||||
instruction_desc( const std::string& name,
|
||||
const std::vector<operand_access>& access_types,
|
||||
int access_size_index,
|
||||
bool is_volatile,
|
||||
const std::string& symbolic_operator,
|
||||
std::vector<int> branch_operands,
|
||||
const std::pair<int, bool>& memory_operands ) :
|
||||
name( name ), access_types( access_types ), access_size_index( access_size_index - 1 ),
|
||||
is_volatile( is_volatile ), symbolic_operator( symbolic_operator ),
|
||||
memory_operand_index( memory_operands.first - 1 ), memory_write( memory_operands.second )
|
||||
{
|
||||
list.push_back( this );
|
||||
fassert( operand_count() <= max_operand_count );
|
||||
|
||||
// Validate all operand indices.
|
||||
//
|
||||
fassert( access_size_index == 0 || abs( access_size_index ) <= operand_count() );
|
||||
fassert( memory_operands.first == 0 || abs( memory_operands.first ) <= operand_count() );
|
||||
for ( int op : branch_operands )
|
||||
fassert( op != 0 && abs( op ) <= operand_count() );
|
||||
|
||||
// Process branch operands.
|
||||
//
|
||||
for ( int op : branch_operands )
|
||||
{
|
||||
if ( op > 0 )
|
||||
branch_operands_vip.push_back( op - 1 );
|
||||
else
|
||||
branch_operands_rip.push_back( -op - 1 );
|
||||
}
|
||||
}
|
||||
|
||||
// Number of operands this instruction has.
|
||||
//
|
||||
int operand_count() const { return access_types.size(); }
|
||||
|
||||
// Whether the instruction branches for not.
|
||||
//
|
||||
bool is_branching_virt() const { return !branch_operands_vip.empty(); }
|
||||
bool is_branching_real() const { return !branch_operands_rip.empty(); }
|
||||
bool is_branching() const { return is_branching_virt() || is_branching_real(); }
|
||||
|
||||
// Whether the instruction acceses/reads/writes memory or not.
|
||||
//
|
||||
bool reads_memory() const { return accesses_memory() && !memory_write; }
|
||||
bool writes_memory() const { return accesses_memory() && memory_write; }
|
||||
bool accesses_memory() const { return memory_operand_index != -1; }
|
||||
|
||||
// Conversion to human-readable format.
|
||||
//
|
||||
std::string to_string( uint8_t access_size ) const
|
||||
{
|
||||
if ( !access_size ) return name;
|
||||
return name + ( char ) format::suffix_map[ access_size ];
|
||||
}
|
||||
};
|
||||
std::vector<const instruction_desc*> instruction_desc::list = {};
|
||||
|
||||
namespace ins
|
||||
{
|
||||
// -- Data/Memory instructions
|
||||
|
|
@ -217,6 +99,24 @@ namespace vtil::arch
|
|||
static const instruction_desc bror = { "ror", { readwrite, read_any }, 1, false, "ror", {}, {} };
|
||||
static const instruction_desc brol = { "rol", { readwrite, read_any }, 1, false, "rol", {}, {} };
|
||||
/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
// -- Flag-creation instructions
|
||||
//
|
||||
// SETS Reg | OP1 = EFLAGS(OP2)[SF]
|
||||
// SETZ Reg | OP1 = EFLAGS(OP2)[ZF]
|
||||
// SETP Reg | OP1 = EFLAGS(OP2)[PF]
|
||||
// SETC Reg | OP1 = EFLAGS(OP2)[CF]
|
||||
// SETO Reg | OP1 = EFLAGS(OP2)[OF]
|
||||
|
||||
//
|
||||
/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
|
||||
/* [Name] [Operands...] [ASizeOp] [Volatile] [Operator] [BranchOps] [MemOps] */
|
||||
static const instruction_desc sets = { "sets", { write, read_any }, 1, false, "sets", {}, {} };
|
||||
static const instruction_desc setz = { "setz", { write, read_any }, 1, false, "setz", {}, {} };
|
||||
static const instruction_desc setp = { "setp", { write, read_any }, 1, false, "setp", {}, {} };
|
||||
static const instruction_desc setc = { "setc", { write, read_any }, 1, false, "setc", {}, {} };
|
||||
static const instruction_desc seto = { "seto", { write, read_any }, 1, false, "seto", {}, {} };
|
||||
/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
// -- Control flow instructions
|
||||
//
|
||||
|
|
@ -227,10 +127,10 @@ namespace vtil::arch
|
|||
//
|
||||
/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
|
||||
/* [Name] [Operands...] [ASizeOp] [Volatile] [Operator] [BranchOps] [MemOps] */
|
||||
static const instruction_desc js = { "js", { read_reg, read_any, read_any }, 2, true, {}, { 1, 2 }, {} };
|
||||
static const instruction_desc jmp = { "jmp", { read_any }, 1, true, {}, { 1 }, {} };
|
||||
static const instruction_desc vexit = { "vexit", { read_any }, 1, true, {}, { -1 }, {} };
|
||||
static const instruction_desc vxcall = { "vxcall", { read_any }, 1, true, {}, {}, {} };
|
||||
static const instruction_desc js = { "js", { read_reg, read_any, read_any }, 2, true, {}, { 1, 2 }, {} };
|
||||
static const instruction_desc jmp = { "jmp", { read_any }, 1, true, {}, { 1 }, {} };
|
||||
static const instruction_desc vexit = { "vexit", { read_any }, 1, true, {}, { -1 }, {} };
|
||||
static const instruction_desc vxcall = { "vxcall", { read_any }, 1, true, {}, {}, {} };
|
||||
/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
|
||||
|
||||
// -- Special instructions
|
||||
|
|
@ -256,4 +156,15 @@ namespace vtil::arch
|
|||
static const instruction_desc vpinwm = { "vpinwm", { read_reg, read_imm }, 1, true, {}, {}, { 1, true } };
|
||||
/*-----------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
|
||||
};
|
||||
|
||||
// List of all instructions.
|
||||
//
|
||||
static const instruction_desc* instruction_list[] =
|
||||
{
|
||||
&ins::mov, &ins::movr, &ins::str, &ins::ldd, &ins::neg, &ins::add, &ins::sub, &ins::mul,
|
||||
&ins::imul, &ins::mulhi, &ins::imulhi, &ins::div, &ins::idiv, &ins::rem, &ins::irem, &ins::bnot,
|
||||
&ins::bshr, &ins::bshl, &ins::bxor, &ins::bor, &ins::band, &ins::bror, &ins::brol, &ins::sets,
|
||||
&ins::setz, &ins::setp, &ins::setc, &ins::seto, &ins::js, &ins::jmp, &ins::vexit, &ins::vxcall,
|
||||
&ins::nop, &ins::vcmp0, &ins::vsetcc, &ins::vemit, &ins::vpinr, &ins::vpinw, &ins::vpinrm, &ins::vpinwm
|
||||
};
|
||||
};
|
||||
|
|
@ -1,8 +1,34 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <capstone.hpp>
|
||||
#include "registers.hpp"
|
||||
#include "..\misc\format.hpp"
|
||||
#include <vtil/amd64>
|
||||
#include "register_view.hpp"
|
||||
|
||||
// Any operand used in a VTIL instruction will be essentialy either a register or an
|
||||
// immediate value, where registers can also be either temporaries, physical registers or
|
||||
|
|
@ -36,7 +62,7 @@ namespace vtil::arch
|
|||
// Operand type is constructed either by a register view or an immediate
|
||||
// followed by an explicit size.
|
||||
//
|
||||
operand() {}
|
||||
operand() = default;
|
||||
operand( const register_view& rw ) : reg( rw ), imm_size( 0 ) {}
|
||||
operand( uint64_t v, uint8_t size ) : u64( v ), imm_size( size ) {}
|
||||
|
||||
|
|
|
|||
77
VTIL-Architecture/arch/register_descriptor.hpp
Normal file
77
VTIL-Architecture/arch/register_descriptor.hpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vtil/amd64>
|
||||
#include "control_registers.hpp"
|
||||
|
||||
namespace vtil::arch
|
||||
{
|
||||
// Register descriptors are used to describe each unique "full" register such as RAX.
|
||||
// Any physical register such as EAX will be extended to its full form (RAX in this case).
|
||||
// - Note: Size of a register is always assumed to be 64-bits.
|
||||
//
|
||||
struct register_desc
|
||||
{
|
||||
// Descriptor's identifier will be used for comparison if the register
|
||||
// instance is not mapped to any physical register.
|
||||
//
|
||||
std::string identifier = "";
|
||||
|
||||
// If this field is not X86_REG_INVALID, it's an indicator that this
|
||||
// register and the alias essentially maps to a physical register.
|
||||
//
|
||||
x86_reg maps_to = X86_REG_INVALID;
|
||||
|
||||
// Either a x86 register identifier or an arbitrary string must be passed
|
||||
// to construct a register descriptor.
|
||||
//
|
||||
register_desc() = default;
|
||||
register_desc( x86_reg reg )
|
||||
{
|
||||
maps_to = reg >= X86_REG_VCR0 ? reg : amd64::extend( reg );
|
||||
identifier = reg >= X86_REG_VCR0 ? lookup_control_register( reg )->identifier : amd64::name( maps_to );
|
||||
}
|
||||
register_desc( const std::string& id ) : identifier( id ) {}
|
||||
|
||||
// Conversion to human-readable format.
|
||||
//
|
||||
std::string to_string() const { return identifier; }
|
||||
|
||||
// Simple helpers to determine the type of register.
|
||||
//
|
||||
bool is_physical() const { return maps_to != X86_REG_INVALID; }
|
||||
bool is_valid() const { return !identifier.empty(); }
|
||||
|
||||
// Basic comparison operators.
|
||||
//
|
||||
bool operator!=( const register_desc& o ) const { return !operator==( o ); }
|
||||
bool operator==( const register_desc& o ) const { return identifier == o.identifier; }
|
||||
bool operator<( const register_desc& o ) const { return identifier < o.identifier; }
|
||||
};
|
||||
};
|
||||
|
|
@ -1,182 +0,0 @@
|
|||
#pragma once
|
||||
#include <map>
|
||||
#include <tuple>
|
||||
#include <capstone.hpp>
|
||||
#include "control_registers.hpp"
|
||||
|
||||
namespace vtil::arch
|
||||
{
|
||||
// List of all physical registers and the base registers they map to <0> at offset <1> of size <2>.
|
||||
//
|
||||
static const std::map<x86_reg, std::tuple<x86_reg, uint8_t, uint8_t>> register_mappings
|
||||
{
|
||||
/* [Instance] [Base] [Offset] [Size] */
|
||||
{ X86_REG_RAX, { X86_REG_RAX, 0, 8 } },
|
||||
{ X86_REG_EAX, { X86_REG_RAX, 0, 4 } },
|
||||
{ X86_REG_AX, { X86_REG_RAX, 0, 2 } },
|
||||
{ X86_REG_AH, { X86_REG_RAX, 1, 1 } },
|
||||
{ X86_REG_AL, { X86_REG_RAX, 0, 1 } },
|
||||
|
||||
{ X86_REG_RBX, { X86_REG_RBX, 0, 8 } },
|
||||
{ X86_REG_EBX, { X86_REG_RBX, 0, 4 } },
|
||||
{ X86_REG_BX, { X86_REG_RBX, 0, 2 } },
|
||||
{ X86_REG_BH, { X86_REG_RBX, 1, 1 } },
|
||||
{ X86_REG_BL, { X86_REG_RBX, 0, 1 } },
|
||||
|
||||
{ X86_REG_RCX, { X86_REG_RCX, 0, 8 } },
|
||||
{ X86_REG_ECX, { X86_REG_RCX, 0, 4 } },
|
||||
{ X86_REG_CX, { X86_REG_RCX, 0, 2 } },
|
||||
{ X86_REG_CH, { X86_REG_RCX, 1, 1 } },
|
||||
{ X86_REG_CL, { X86_REG_RCX, 0, 1 } },
|
||||
|
||||
{ X86_REG_RDX, { X86_REG_RDX, 0, 8 } },
|
||||
{ X86_REG_EDX, { X86_REG_RDX, 0, 4 } },
|
||||
{ X86_REG_DX, { X86_REG_RDX, 0, 2 } },
|
||||
{ X86_REG_DH, { X86_REG_RDX, 1, 1 } },
|
||||
{ X86_REG_DL, { X86_REG_RDX, 0, 1 } },
|
||||
|
||||
{ X86_REG_RDI, { X86_REG_RDI, 0, 8 } },
|
||||
{ X86_REG_EDI, { X86_REG_RDI, 0, 4 } },
|
||||
{ X86_REG_DI, { X86_REG_RDI, 0, 2 } },
|
||||
{ X86_REG_DIL, { X86_REG_RDI, 0, 1 } },
|
||||
|
||||
{ X86_REG_RSI, { X86_REG_RSI, 0, 8 } },
|
||||
{ X86_REG_ESI, { X86_REG_RSI, 0, 4 } },
|
||||
{ X86_REG_SI, { X86_REG_RSI, 0, 2 } },
|
||||
{ X86_REG_SIL, { X86_REG_RSI, 0, 1 } },
|
||||
|
||||
{ X86_REG_RBP, { X86_REG_RBP, 0, 8 } },
|
||||
{ X86_REG_EBP, { X86_REG_RBP, 0, 4 } },
|
||||
{ X86_REG_BP, { X86_REG_RBP, 0, 2 } },
|
||||
{ X86_REG_BPL, { X86_REG_RBP, 0, 1 } },
|
||||
|
||||
{ X86_REG_RSP, { X86_REG_RSP, 0, 8 } },
|
||||
{ X86_REG_ESP, { X86_REG_RSP, 0, 4 } },
|
||||
{ X86_REG_SP, { X86_REG_RSP, 0, 2 } },
|
||||
{ X86_REG_SPL, { X86_REG_RSP, 0, 1 } },
|
||||
|
||||
{ X86_REG_R8, { X86_REG_R8, 0, 8 } },
|
||||
{ X86_REG_R8D, { X86_REG_R8, 0, 4 } },
|
||||
{ X86_REG_R8W, { X86_REG_R8, 0, 2 } },
|
||||
{ X86_REG_R8B, { X86_REG_R8, 0, 1 } },
|
||||
|
||||
{ X86_REG_R9, { X86_REG_R9, 0, 8 } },
|
||||
{ X86_REG_R9D, { X86_REG_R9, 0, 4 } },
|
||||
{ X86_REG_R9W, { X86_REG_R9, 0, 2 } },
|
||||
{ X86_REG_R9B, { X86_REG_R9, 0, 1 } },
|
||||
|
||||
{ X86_REG_R10, { X86_REG_R10, 0, 8 } },
|
||||
{ X86_REG_R10D, { X86_REG_R10, 0, 4 } },
|
||||
{ X86_REG_R10W, { X86_REG_R10, 0, 2 } },
|
||||
{ X86_REG_R10B, { X86_REG_R10, 0, 1 } },
|
||||
|
||||
{ X86_REG_R11, { X86_REG_R11, 0, 8 } },
|
||||
{ X86_REG_R11D, { X86_REG_R11, 0, 4 } },
|
||||
{ X86_REG_R11W, { X86_REG_R11, 0, 2 } },
|
||||
{ X86_REG_R11B, { X86_REG_R11, 0, 1 } },
|
||||
|
||||
{ X86_REG_R12, { X86_REG_R12, 0, 8 } },
|
||||
{ X86_REG_R12D, { X86_REG_R12, 0, 4 } },
|
||||
{ X86_REG_R12W, { X86_REG_R12, 0, 2 } },
|
||||
{ X86_REG_R12B, { X86_REG_R12, 0, 1 } },
|
||||
|
||||
{ X86_REG_R13, { X86_REG_R13, 0, 8 } },
|
||||
{ X86_REG_R13D, { X86_REG_R13, 0, 4 } },
|
||||
{ X86_REG_R13W, { X86_REG_R13, 0, 2 } },
|
||||
{ X86_REG_R13B, { X86_REG_R13, 0, 1 } },
|
||||
|
||||
{ X86_REG_R14, { X86_REG_R14, 0, 8 } },
|
||||
{ X86_REG_R14D, { X86_REG_R14, 0, 4 } },
|
||||
{ X86_REG_R14W, { X86_REG_R14, 0, 2 } },
|
||||
{ X86_REG_R14B, { X86_REG_R14, 0, 1 } },
|
||||
|
||||
{ X86_REG_R15, { X86_REG_R15, 0, 8 } },
|
||||
{ X86_REG_R15D, { X86_REG_R15, 0, 4 } },
|
||||
{ X86_REG_R15W, { X86_REG_R15, 0, 2 } },
|
||||
{ X86_REG_R15B, { X86_REG_R15, 0, 1 } },
|
||||
|
||||
{ X86_REG_EFLAGS, { X86_REG_EFLAGS, 0, 8 } },
|
||||
};
|
||||
|
||||
// Gets the offset<0> and size<1> of the mapping for the given register.
|
||||
//
|
||||
template<typename T>
|
||||
static std::pair<uint8_t, uint8_t> get_register_mapping( T _reg )
|
||||
{
|
||||
// Return default mapping if it's a control register.
|
||||
//
|
||||
if ( _reg > X86_REG_VCR0 )
|
||||
return { 0, 8 };
|
||||
|
||||
// Try to find the register mapping, if succesful return if
|
||||
//
|
||||
auto it = register_mappings.find( ( x86_reg ) _reg );
|
||||
if( it != register_mappings.end() )
|
||||
return { std::get<1>( it->second ), std::get<2>( it->second ) };
|
||||
|
||||
// Otherwise return default mapping after making sure it's valid.
|
||||
//
|
||||
fassert( _reg != X86_REG_INVALID );
|
||||
return { 0, 8 };
|
||||
}
|
||||
|
||||
// Gets the base register for the given register.
|
||||
//
|
||||
template<typename T>
|
||||
static x86_reg extend_register( T _reg )
|
||||
{
|
||||
// Try to find the register mapping,
|
||||
// return as is if we fail to do so.
|
||||
//
|
||||
auto it = register_mappings.find( ( x86_reg ) _reg );
|
||||
if ( it == register_mappings.end() )
|
||||
return ( x86_reg ) _reg;
|
||||
|
||||
// Otherwise return the base register.
|
||||
//
|
||||
return std::get<0>( it->second );
|
||||
}
|
||||
|
||||
// Converts the enum into human-readable format.
|
||||
//
|
||||
template<typename T>
|
||||
static std::string name_register( T _reg )
|
||||
{
|
||||
// Return control register name if it's one.
|
||||
//
|
||||
if ( _reg >= X86_REG_VCR0 )
|
||||
return lookup_control_register( ( x86_reg ) _reg )->identifier;
|
||||
|
||||
// Else lookup the name from capstone.
|
||||
//
|
||||
return cs_reg_name( disasm, ( x86_reg ) _reg );
|
||||
}
|
||||
|
||||
// Remaps the given register at given specifications.
|
||||
//
|
||||
template<typename T>
|
||||
static x86_reg remap_register( T _reg, uint8_t offset, uint8_t size )
|
||||
{
|
||||
// Extend passed register
|
||||
//
|
||||
x86_reg base_register = extend_register( _reg );
|
||||
|
||||
// For each mapping described:
|
||||
//
|
||||
for ( auto& pair : register_mappings )
|
||||
{
|
||||
// If matches the specifications, return.
|
||||
//
|
||||
if ( std::get<0>( pair.second ) == base_register &&
|
||||
std::get<1>( pair.second ) == offset &&
|
||||
std::get<2>( pair.second ) == size )
|
||||
return pair.first;
|
||||
}
|
||||
|
||||
// If we fail to find, and we're strictly
|
||||
// remapping to a full register, return as is.
|
||||
//
|
||||
fassert( offset == 0 && size == 8);
|
||||
return base_register;
|
||||
}
|
||||
};
|
||||
82
VTIL-Architecture/arch/register_view.cpp
Normal file
82
VTIL-Architecture/arch/register_view.cpp
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#include "register_view.hpp"
|
||||
#include <vtil/math>
|
||||
#include <vtil/amd64>
|
||||
|
||||
namespace vtil::arch
|
||||
{
|
||||
// Basically an extended version of the register descriptor constructor
|
||||
// with the addition of an offset and a size value.
|
||||
//
|
||||
register_view::register_view( x86_reg base, uint8_t offset, uint8_t size ) : base( base ), size( size ), offset( offset ) { fassert( is_valid() ); }
|
||||
register_view::register_view( const std::string& base, uint8_t offset, uint8_t size ) : base( base ), size( size ), offset( offset ) { fassert( is_valid() ); }
|
||||
register_view::register_view( const register_desc& base, uint8_t offset, uint8_t size ) : base( base ), size( size ), offset( offset ) { fassert( is_valid() ); }
|
||||
|
||||
// Basic comparison operators.
|
||||
//
|
||||
bool register_view::operator<( const register_view& o ) const
|
||||
{
|
||||
// Try to sort based on base identifier first.
|
||||
//
|
||||
if ( base.identifier != o.base.identifier )
|
||||
return base.identifier < o.base.identifier;
|
||||
|
||||
// If matching, check offset and size.
|
||||
//
|
||||
uint64_t mask_0 = math::mask( size, offset );
|
||||
uint64_t mask_1 = math::mask( o.size, o.offset );
|
||||
return mask_0 < mask_1;
|
||||
}
|
||||
bool register_view::operator==( const register_view& o ) const
|
||||
{
|
||||
return base.identifier == o.base.identifier && offset == o.offset && size == o.size;
|
||||
}
|
||||
bool register_view::operator!=( const register_view& o ) const { return !operator==( o ); };
|
||||
|
||||
// Conversion to human-readable format.
|
||||
//
|
||||
std::string register_view::to_string( bool explicit_size ) const
|
||||
{
|
||||
if ( base.is_physical() )
|
||||
{
|
||||
if ( base.maps_to >= X86_REG_VCR0 )
|
||||
return lookup_control_register( base.maps_to )->identifier;
|
||||
|
||||
x86_reg reg = amd64::remap( base.maps_to, offset, size );
|
||||
return amd64::name( reg );
|
||||
}
|
||||
|
||||
std::string out = base.identifier;
|
||||
if ( explicit_size && size != 8 )
|
||||
out += format::suffix_map[ size ];
|
||||
if ( offset != 0 )
|
||||
out += "@" + std::to_string( offset );
|
||||
return out;
|
||||
}
|
||||
};
|
||||
93
VTIL-Architecture/arch/register_view.hpp
Normal file
93
VTIL-Architecture/arch/register_view.hpp
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include "control_registers.hpp"
|
||||
#include "register_descriptor.hpp"
|
||||
|
||||
namespace vtil::arch
|
||||
{
|
||||
// Register views are used to describe well-defined segments of registers.
|
||||
// - AX views RAX @ {0, 2}, BH views RBX @ {1, 1} so on.
|
||||
//
|
||||
struct register_view
|
||||
{
|
||||
// The base register descriptor.
|
||||
//
|
||||
register_desc base = {};
|
||||
|
||||
// Offset into that register and the segment referenced.
|
||||
//
|
||||
uint8_t offset = 0;
|
||||
uint8_t size = 8;
|
||||
|
||||
// Basically an extended version of the register descriptor constructor
|
||||
// with the addition of an offset and a size value.
|
||||
//
|
||||
register_view() = default;
|
||||
register_view( x86_reg base, uint8_t offset = 0, uint8_t size = 8 );
|
||||
register_view( const std::string& base, uint8_t offset = 0, uint8_t size = 8 );
|
||||
register_view( const register_desc& base, uint8_t offset = 0, uint8_t size = 8 );
|
||||
|
||||
// Mask that describes how we map to the base register and a
|
||||
// basic "overlapping" check using this mask.
|
||||
//
|
||||
uint64_t get_mask() const { return ( ~0ull >> ( 64 - size * 8 ) ) << ( offset * 8 ); }
|
||||
bool overlaps( const register_view& o ) const { return base == o.base && ( get_mask() & o.get_mask() ); }
|
||||
|
||||
// Conversion to human-readable format.
|
||||
//
|
||||
std::string to_string( bool explicit_size = false ) const;
|
||||
|
||||
// Validity check.
|
||||
//
|
||||
bool is_valid() const { return base.is_valid() && ( offset + size ) <= 8; }
|
||||
|
||||
// Basic comparison operators.
|
||||
//
|
||||
bool operator!=( const register_view& o ) const;
|
||||
bool operator==( const register_view& o ) const;
|
||||
bool operator<( const register_view& o ) const;
|
||||
};
|
||||
};
|
||||
|
||||
// Testing new flags system, will remove. [TODO]
|
||||
//
|
||||
namespace vtil
|
||||
{
|
||||
static const arch::register_view REG_UNKB = { arch::create_control_register( { "unkb", true } ), 0, 1 };
|
||||
static const arch::register_view REG_UNKW = { arch::create_control_register( { "unkw", true } ), 0, 2 };
|
||||
static const arch::register_view REG_UNKD = { arch::create_control_register( { "unkd", true } ), 0, 4 };
|
||||
static const arch::register_view REG_UNKQ = { arch::create_control_register( { "unkq", true } ), 0, 8 };
|
||||
static const arch::register_view REG_ZF = { arch::create_control_register( { "eflags.zf", false } ), 0, 1 };
|
||||
static const arch::register_view REG_SF = { arch::create_control_register( { "eflags.sf", false } ), 0, 1 };
|
||||
static const arch::register_view REG_PF = { arch::create_control_register( { "eflags.pf", false } ), 0, 1 };
|
||||
static const arch::register_view REG_AF = { arch::create_control_register( { "eflags.af", false } ), 0, 1 };
|
||||
static const arch::register_view REG_OF = { arch::create_control_register( { "eflags.of", false } ), 0, 1 };
|
||||
static const arch::register_view REG_CF = { arch::create_control_register( { "eflags.cf", false } ), 0, 1 };
|
||||
};
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <capstone.hpp>
|
||||
#include "..\misc\format.hpp"
|
||||
#include "register_details.hpp"
|
||||
|
||||
namespace vtil::arch
|
||||
{
|
||||
// Register descriptors are used to describe each unique "full" register such as RAX.
|
||||
// Any physical register such as EAX will be extended to its full form (RAX in this case).
|
||||
// - Note: Size of a register is always assumed to be 64-bits.
|
||||
//
|
||||
struct register_desc
|
||||
{
|
||||
// Descriptor's identifier will be used for comparison if the register
|
||||
// instance is not mapped to any physical register.
|
||||
//
|
||||
std::string identifier = "";
|
||||
|
||||
// If this field is not X86_REG_INVALID, it's an indicator that this
|
||||
// register and the alias essentially maps to a physical register.
|
||||
//
|
||||
x86_reg maps_to = X86_REG_INVALID;
|
||||
|
||||
// Either a x86 register identifier or an arbitrary string must be passed
|
||||
// to construct a register descriptor.
|
||||
//
|
||||
register_desc() {}
|
||||
register_desc( x86_reg reg ) { maps_to = extend_register( reg ); identifier = name_register( maps_to ); }
|
||||
register_desc( const std::string& id ) : identifier( id ) {}
|
||||
|
||||
// Conversion to human-readable format.
|
||||
//
|
||||
std::string to_string() const { return identifier; }
|
||||
|
||||
// Simple helpers to determine the type of register.
|
||||
//
|
||||
bool is_physical() const { return maps_to != X86_REG_INVALID; }
|
||||
bool is_valid() const { return !identifier.empty(); }
|
||||
|
||||
// Basic comparison operators.
|
||||
//
|
||||
bool operator!=( const register_desc& o ) const { return !operator==( o ); }
|
||||
bool operator==( const register_desc& o ) const { return identifier == o.identifier; }
|
||||
bool operator<( const register_desc& o ) const { return identifier < o.identifier; }
|
||||
};
|
||||
|
||||
// Register views are used to describe well-defined segments of registers.
|
||||
// - AX views RAX @ {0, 2}, BH views RBX @ {1, 1} so on.
|
||||
//
|
||||
struct register_view
|
||||
{
|
||||
// The base register descriptor.
|
||||
//
|
||||
register_desc base = {};
|
||||
|
||||
// Offset into that register and the segment referenced.
|
||||
//
|
||||
uint8_t offset = 0;
|
||||
uint8_t size = 8;
|
||||
|
||||
// Basically an extended version of the register descriptor constructor
|
||||
// with the addition of an offset and a size value.
|
||||
//
|
||||
register_view() {}
|
||||
register_view( x86_reg base, uint8_t offset = 0, uint8_t size = 8 ) : base( base ), size( size ), offset( offset ) { fassert( is_valid() ); }
|
||||
register_view( const std::string& base, uint8_t offset = 0, uint8_t size = 8 ) : base( base ), size( size ), offset( offset ) { fassert( is_valid() ); }
|
||||
register_view( const register_desc& base, uint8_t offset = 0, uint8_t size = 8 ) : base( base ), size( size ), offset( offset ) { fassert( is_valid() ); }
|
||||
|
||||
// Mask that describes how we map to the base register and a
|
||||
// basic "overlapping" check using this mask.
|
||||
//
|
||||
uint64_t get_mask() const { return ( ~0ull >> ( 64 - size * 8 ) ) << ( offset * 8 ); }
|
||||
bool overlaps( const register_view& o ) const { return base == o.base && ( get_mask() & o.get_mask() ); }
|
||||
|
||||
// Conversion to human-readable format.
|
||||
//
|
||||
std::string to_string( bool explicit_size = false ) const
|
||||
{
|
||||
if ( base.is_physical() )
|
||||
return name_register( remap_register( base.maps_to, offset, size ) );
|
||||
std::string out = base.identifier;
|
||||
if ( explicit_size && size != 8 )
|
||||
out += format::suffix_map[ size ];
|
||||
if ( offset != 0 )
|
||||
out += "@" + std::to_string( offset );
|
||||
return out;
|
||||
}
|
||||
|
||||
// Validity check.
|
||||
//
|
||||
bool is_valid() const { return base.is_valid() && ( offset + size ) <= 8; }
|
||||
|
||||
// Basic comparison operators.
|
||||
//
|
||||
bool operator!=( const register_view& o ) const { return !operator==( o ); };
|
||||
bool operator==( const register_view& o ) const { return base.identifier == o.base.identifier && offset == o.offset && size == o.size; }
|
||||
bool operator<( const register_view& o ) const { return base.identifier == o.base.identifier ? ( offset == o.offset ? size < o.size : offset < o.offset ) : base.identifier < o.base.identifier; }
|
||||
};
|
||||
};
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <capstone/capstone.h>
|
||||
#include "platform.hpp"
|
||||
#pragma comment(lib, "capstone_i.lib")
|
||||
|
||||
namespace capstone
|
||||
{
|
||||
struct instruction
|
||||
{
|
||||
// Base object
|
||||
uint32_t id = 0;
|
||||
uint64_t address = 0;
|
||||
std::vector<uint8_t> bytes;
|
||||
std::string mnemonic;
|
||||
std::string operand_string;
|
||||
|
||||
// From ->detail
|
||||
std::vector<uint16_t> regs_read;
|
||||
std::vector<uint16_t> regs_write;
|
||||
std::vector<uint8_t> groups;
|
||||
cs_x86 details;
|
||||
|
||||
instruction() {};
|
||||
instruction( const cs_insn& ins ) :
|
||||
id( ins.id ), address( ins.address ),
|
||||
mnemonic( ins.mnemonic ), operand_string( ins.op_str ),
|
||||
bytes( ins.bytes, ins.bytes + ins.size ),
|
||||
regs_read( ins.detail->regs_read, ins.detail->regs_read + ins.detail->regs_read_count ),
|
||||
regs_write( ins.detail->regs_write, ins.detail->regs_write + ins.detail->regs_write_count ),
|
||||
groups( ins.detail->groups, ins.detail->groups + ins.detail->groups_count ),
|
||||
details( ins.detail->x86 )
|
||||
{
|
||||
}
|
||||
|
||||
std::string dump() const
|
||||
{
|
||||
char bfr[ 64 ];
|
||||
sprintf_s( bfr, "%p: %s\t%s", address, mnemonic.data(), operand_string.data() );
|
||||
return bfr;
|
||||
}
|
||||
|
||||
bool is( uint32_t idx, const std::vector<x86_op_type>& operands ) const
|
||||
{
|
||||
if ( id != idx ) return false;
|
||||
if ( details.op_count != operands.size() ) return false;
|
||||
for ( int i = 0; i < details.op_count; i++ )
|
||||
if ( details.operands[ i ].type != operands[ i ] )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool in_group( uint8_t g ) const
|
||||
{
|
||||
for ( auto o : groups )
|
||||
if ( o == g ) return true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct context
|
||||
{
|
||||
csh handle = 0;
|
||||
void destroy() { cs_close( &handle ); }
|
||||
|
||||
operator csh() { return handle; }
|
||||
|
||||
std::vector<instruction> operator()( const void* bytes, uint64_t address, size_t size = 0, size_t count = 1 )
|
||||
{
|
||||
std::vector<instruction> out;
|
||||
|
||||
cs_insn* ins;
|
||||
count = cs_disasm
|
||||
(
|
||||
handle,
|
||||
( uint8_t* ) bytes,
|
||||
size ? size : -1,
|
||||
address,
|
||||
size ? 0 : count,
|
||||
&ins
|
||||
);
|
||||
|
||||
for ( int i = 0; i < count; i++ )
|
||||
out.push_back( ins[ i ] );
|
||||
cs_free( ins, count );
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
static context create( cs_arch arch, cs_mode mode )
|
||||
{
|
||||
context ctx;
|
||||
if ( !cs_open( arch, mode, &ctx.handle ) )
|
||||
cs_option( ctx.handle, CS_OPT_DETAIL, CS_OPT_ON );
|
||||
else
|
||||
throw "Failed to create the disassembler!";
|
||||
return ctx;
|
||||
}
|
||||
};
|
||||
|
||||
static auto disasm = capstone::create( CS_ARCH_X86, CS_MODE_64 );
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <keystone/keystone.h>
|
||||
#pragma comment(lib, "keystone.lib")
|
||||
|
||||
namespace keystone
|
||||
{
|
||||
struct context
|
||||
{
|
||||
ks_engine* handle = 0;
|
||||
void destroy() { ks_close( handle ); }
|
||||
|
||||
operator ks_engine*() { return handle; }
|
||||
|
||||
std::vector<uint8_t> operator()( const std::string& src, uint64_t va = 0 )
|
||||
{
|
||||
std::vector<uint8_t> out;
|
||||
|
||||
size_t count;
|
||||
unsigned char* encode;
|
||||
size_t size;
|
||||
if ( ks_asm( handle, ( ".code64;" + src ).data(), va, &encode, &size, &count ) )
|
||||
{
|
||||
ks_free( encode );
|
||||
return {};
|
||||
}
|
||||
|
||||
if ( size )
|
||||
{
|
||||
out = { encode, encode + size };
|
||||
ks_free( encode );
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
static context create( ks_arch arch, ks_mode mode )
|
||||
{
|
||||
context ctx;
|
||||
if ( ks_open( arch, mode, &ctx.handle ) )
|
||||
throw "Failed to create the assembler!";
|
||||
return ctx;
|
||||
}
|
||||
};
|
||||
|
||||
static auto assemble = keystone::create( KS_ARCH_X86, KS_MODE_64 );
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#if _WIN64
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
enum console_color
|
||||
{
|
||||
CON_BRG = 15,
|
||||
CON_YLW = 14,
|
||||
CON_PRP = 13,
|
||||
CON_RED = 12,
|
||||
CON_CYN = 11,
|
||||
CON_GRN = 10,
|
||||
CON_BLU = 9,
|
||||
CON_DEF = 7,
|
||||
};
|
||||
|
||||
namespace mem
|
||||
{
|
||||
static void* allocate_rwx( size_t size )
|
||||
{
|
||||
#if _WIN64
|
||||
return VirtualAlloc( 0, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE );
|
||||
#else
|
||||
return mmap( 0, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0 );
|
||||
#endif
|
||||
}
|
||||
|
||||
static void free_rwx( void* pointer, size_t size )
|
||||
{
|
||||
#if _WIN64
|
||||
VirtualFree( pointer, 0, MEM_FREE | MEM_RELEASE );
|
||||
#else
|
||||
mmunmap( pointer, size );
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
namespace io
|
||||
{
|
||||
template<bool critical = false>
|
||||
static void bp()
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
__asm { int 3 };
|
||||
#else
|
||||
if constexpr ( critical )
|
||||
exit( -1 );
|
||||
#endif
|
||||
}
|
||||
|
||||
static int log_padding = 0;
|
||||
static std::mutex print_mutex;
|
||||
static bool log_init = false;
|
||||
|
||||
template<typename T>
|
||||
__forceinline static auto fix_format_paramter( const T& x )
|
||||
{
|
||||
if constexpr ( std::is_same_v<T, std::string> ||
|
||||
std::is_same_v<T, std::wstring> )
|
||||
return x.data();
|
||||
else
|
||||
return x;
|
||||
}
|
||||
|
||||
template<console_color color = CON_DEF, typename... params>
|
||||
static int log( const char* fmt, params&&... ps )
|
||||
{
|
||||
std::lock_guard g( print_mutex );
|
||||
#if _WIN64
|
||||
SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), color );
|
||||
if ( !log_init )
|
||||
{
|
||||
SetConsoleOutputCP( CP_UTF8 );
|
||||
log_init = true;
|
||||
}
|
||||
#endif
|
||||
int v = printf( fmt, fix_format_paramter( ps )... );
|
||||
if ( fmt[ strlen( fmt ) - 1 ] == '\n' && log_padding > 0 )
|
||||
v += printf( "%*c", log_padding * 8, ' ' );
|
||||
return v;
|
||||
}
|
||||
|
||||
template<bool critical = true, typename... params>
|
||||
static void error( const char* fmt, params&&... ps )
|
||||
{
|
||||
log<CON_RED>( fmt, std::forward<params>( ps )... );
|
||||
bp<critical>();
|
||||
}
|
||||
|
||||
static void assert_helper( bool condition, const char* file_name, const char* condition_str, uint32_t line_number )
|
||||
{
|
||||
if ( condition ) return;
|
||||
error
|
||||
(
|
||||
"Assertion failure at %s:%d (%s)",
|
||||
file_name,
|
||||
line_number,
|
||||
condition_str
|
||||
);
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> read_raw( const std::wstring& file_path )
|
||||
{
|
||||
// Try to open file as binary
|
||||
std::ifstream file( file_path, std::ios::binary );
|
||||
if ( !file.good() ) throw "Input file cannot be opened.";
|
||||
|
||||
// Read the whole file
|
||||
std::vector<uint8_t> bytes = std::vector<uint8_t>( std::istreambuf_iterator<char>( file ), {} );
|
||||
if ( bytes.size() == 0 ) throw "Input file is empty.";
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static void write_raw( void* data, size_t size, const std::wstring& file_path )
|
||||
{
|
||||
std::ofstream file( file_path, std::ios::binary );
|
||||
if ( !file.good() ) throw "Output file cannot be opened.";
|
||||
file.write( ( char* ) data, size );
|
||||
}
|
||||
};
|
||||
|
||||
#define fassert__stringify(x) #x
|
||||
#define fassert(...) io::assert_helper( (__VA_ARGS__), __FILE__, fassert__stringify(__VA_ARGS__), __LINE__ )
|
||||
#define unreachable() fassert( false )
|
||||
|
|
@ -1,33 +1,61 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <platform.hpp>
|
||||
#include <vtil/io>
|
||||
#include "..\arch\instruction_set.hpp"
|
||||
#include "..\routine\basic_block.hpp"
|
||||
#include "..\routine\instruction.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
namespace vtil::debug
|
||||
{
|
||||
void dump( const instruction& ins, const instruction* prev = nullptr )
|
||||
static void dump( const instruction& ins, const instruction* prev = nullptr )
|
||||
{
|
||||
using namespace vtil::logger;
|
||||
|
||||
// Print stack pointer offset
|
||||
//
|
||||
if ( ins.sp_reset )
|
||||
io::log<CON_PRP>( ">%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
|
||||
log<CON_PRP>( ">%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
|
||||
else if ( ( prev ? prev->sp_offset : 0 ) == ins.sp_offset )
|
||||
io::log<CON_DEF>( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
|
||||
log<CON_DEF>( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
|
||||
else if ( ( prev ? prev->sp_offset : 0 ) > ins.sp_offset )
|
||||
io::log<CON_RED>( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
|
||||
log<CON_RED>( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
|
||||
else
|
||||
io::log<CON_BLU>( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
|
||||
log<CON_BLU>( "%c0x%-4x ", ins.sp_offset >= 0 ? '+' : '-', abs( ins.sp_offset ) );
|
||||
|
||||
// Print name
|
||||
//
|
||||
if ( ins.is_volatile() )
|
||||
io::log<CON_RED>( FMT_INS_MNM " ", ins.base->to_string( ins.access_size() ) ); // Volatile instruction
|
||||
log<CON_RED>( FMT_INS_MNM " ", ins.base->to_string( ins.access_size() ) ); // Volatile instruction
|
||||
else
|
||||
io::log<CON_BRG>( FMT_INS_MNM " ", ins.base->to_string( ins.access_size() ) ); // Non-volatile instruction
|
||||
log<CON_BRG>( FMT_INS_MNM " ", ins.base->to_string( ins.access_size() ) ); // Non-volatile instruction
|
||||
|
||||
// Print each operand
|
||||
//
|
||||
|
|
@ -36,11 +64,11 @@ namespace vtil::debug
|
|||
if ( op.is_register() )
|
||||
{
|
||||
if ( op.reg.base.maps_to == X86_REG_RSP )
|
||||
io::log<CON_PRP>( FMT_INS_OPR " ", op.reg.to_string() ); // Stack pointer
|
||||
log<CON_PRP>( FMT_INS_OPR " ", op.reg.to_string() ); // Stack pointer
|
||||
else if ( op.reg.base.maps_to != X86_REG_INVALID )
|
||||
io::log<CON_BLU>( FMT_INS_OPR " ", op.reg.to_string() ); // Any hardware/special register
|
||||
log<CON_BLU>( FMT_INS_OPR " ", op.reg.to_string() ); // Any hardware/special register
|
||||
else
|
||||
io::log<CON_GRN>( FMT_INS_OPR " ", op.reg.to_string() ); // Virtual register
|
||||
log<CON_GRN>( FMT_INS_OPR " ", op.reg.to_string() ); // Virtual register
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -51,13 +79,13 @@ namespace vtil::debug
|
|||
ins.operands[ ins.base->memory_operand_index ].reg == X86_REG_RSP )
|
||||
{
|
||||
if ( op.i64 >= 0 )
|
||||
io::log<CON_YLW>( FMT_INS_OPR " ", format::hex( op.i64 ) ); // External stack
|
||||
log<CON_YLW>( FMT_INS_OPR " ", format::hex( op.i64 ) ); // External stack
|
||||
else
|
||||
io::log<CON_BRG>( FMT_INS_OPR " ", format::hex( op.i64 ) ); // VM stack
|
||||
log<CON_BRG>( FMT_INS_OPR " ", format::hex( op.i64 ) ); // VM stack
|
||||
}
|
||||
else
|
||||
{
|
||||
io::log<CON_CYN>( FMT_INS_OPR " ", format::hex( op.i64 ) ); // Any immediate
|
||||
log<CON_CYN>( FMT_INS_OPR " ", format::hex( op.i64 ) ); // Any immediate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -66,30 +94,32 @@ namespace vtil::debug
|
|||
//
|
||||
fassert( ins.operands.size() <= arch::max_operand_count );
|
||||
for ( int i = ins.operands.size(); i < arch::max_operand_count; i++ )
|
||||
io::log( FMT_INS_OPR " ", "" );
|
||||
io::log( "\n" );
|
||||
log( FMT_INS_OPR " ", "" );
|
||||
log( "\n" );
|
||||
}
|
||||
|
||||
void dump( const basic_block* blk, std::set<const basic_block*>* visited = nullptr )
|
||||
static void dump( const basic_block* blk, std::set<const basic_block*>* visited = nullptr )
|
||||
{
|
||||
using namespace vtil::logger;
|
||||
|
||||
bool blk_visited = visited ? visited->find( blk ) != visited->end() : false;
|
||||
|
||||
auto end_with_bool = [ ] ( bool b )
|
||||
{
|
||||
if ( b ) io::log<CON_GRN>( "Y\n" );
|
||||
else io::log<CON_RED>( "N\n" );
|
||||
if ( b ) log<CON_GRN>( "Y\n" );
|
||||
else log<CON_RED>( "N\n" );
|
||||
};
|
||||
|
||||
io::log<CON_DEF>( "Entry point VIP: " );
|
||||
io::log<CON_CYN>( "0x%llx\n", blk->entry_vip );
|
||||
io::log<CON_DEF>( "Stack pointer: " );
|
||||
log<CON_DEF>( "Entry point VIP: " );
|
||||
log<CON_CYN>( "0x%llx\n", blk->entry_vip );
|
||||
log<CON_DEF>( "Stack pointer: " );
|
||||
if ( blk->sp_offset < 0 )
|
||||
io::log<CON_RED>( "%s\n", format::hex( blk->sp_offset ) );
|
||||
log<CON_RED>( "%s\n", format::hex( blk->sp_offset ) );
|
||||
else
|
||||
io::log<CON_GRN>( "%s\n", format::hex( blk->sp_offset ) );
|
||||
io::log<CON_DEF>( "Already visited?: " );
|
||||
log<CON_GRN>( "%s\n", format::hex( blk->sp_offset ) );
|
||||
log<CON_DEF>( "Already visited?: " );
|
||||
end_with_bool( blk_visited );
|
||||
io::log<CON_DEF>( "------------------------\n" );
|
||||
log<CON_DEF>( "------------------------\n" );
|
||||
|
||||
if ( blk_visited )
|
||||
return;
|
||||
|
|
@ -99,11 +129,11 @@ namespace vtil::debug
|
|||
int ins_idx = 0;
|
||||
for ( auto it = blk->begin(); it != blk->end(); it++, ins_idx++ )
|
||||
{
|
||||
io::log<CON_BLU>( "%04d: ", ins_idx );
|
||||
log<CON_BLU>( "%04d: ", ins_idx );
|
||||
if ( it->vip == invalid_vip )
|
||||
io::log<CON_DEF>( "[PSEUDO] " );
|
||||
log<CON_DEF>( "[PSEUDO] " );
|
||||
else
|
||||
io::log<CON_DEF>( "[%06x] ", it->vip );
|
||||
log<CON_DEF>( "[%06x] ", it->vip );
|
||||
dump( *it, it.is_begin() ? nullptr : &*std::prev( it ) );
|
||||
}
|
||||
|
||||
|
|
@ -112,16 +142,16 @@ namespace vtil::debug
|
|||
if ( visited )
|
||||
{
|
||||
visited->insert( blk );
|
||||
io::log_padding++;
|
||||
io::log( "\n" );
|
||||
log_padding++;
|
||||
log( "\n" );
|
||||
for ( auto& child : blk->next )
|
||||
dump( child, visited );
|
||||
io::log_padding--;
|
||||
io::log( "\n" );
|
||||
log_padding--;
|
||||
log( "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
void dump( const routine* routine )
|
||||
static void dump( const routine* routine )
|
||||
{
|
||||
std::set<const basic_block*> vs;
|
||||
dump( routine->entry_point, &vs );
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <platform.h>
|
||||
|
||||
|
||||
#define FMT_TEMP_REG "t%d"
|
||||
#define FMT_INS_MNM "%-8s"
|
||||
#define FMT_INS_OPR "%-12s"
|
||||
#define FMT_INS FMT_INS_MNM " " FMT_INS_OPR " " FMT_INS_OPR " " FMT_INS_OPR " " FMT_INS_OPR
|
||||
|
||||
namespace vtil::format
|
||||
{
|
||||
static constexpr char suffix_map[] = { ' ', 'b', 'w', ' ', 'd', ' ', ' ', ' ', 'q' };
|
||||
|
||||
template<typename... params>
|
||||
static std::string str( const char* fmt, params... ps )
|
||||
{
|
||||
char buffer[ 512 ];
|
||||
sprintf_s( buffer, fmt, io::fix_format_paramter( ps )... );
|
||||
return buffer;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static std::string hex( T value )
|
||||
{
|
||||
if ( !std::is_signed_v<T> || value >= 0 )
|
||||
return str( "0x%llx", value );
|
||||
else
|
||||
return str( "-0x%llx", -value );
|
||||
}
|
||||
|
||||
static std::string offset( int64_t value )
|
||||
{
|
||||
if ( value >= 0 )
|
||||
return str( "+ 0x%llx", value );
|
||||
else
|
||||
return str( "- 0x%llx", -value );
|
||||
}
|
||||
};
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
#pragma once
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
#include <functional>
|
||||
#include "ranges.hpp"
|
||||
|
||||
namespace vtil::query
|
||||
{
|
||||
// Query descriptor is a non-projected-type dependent structure
|
||||
// that describes the base state of any query object.
|
||||
//
|
||||
template<typename _iterator_type>
|
||||
struct query_desc
|
||||
{
|
||||
using iterator_type = _iterator_type;
|
||||
using reference_type = decltype( *std::declval<iterator_type>() );
|
||||
|
||||
// Range iterator itself and the saved instance for at()
|
||||
//
|
||||
iterator_type iterator = {};
|
||||
|
||||
// Direction of iteration:
|
||||
// => +1 for forward
|
||||
// => -1 for backwar
|
||||
//
|
||||
int8_t direction = 0;
|
||||
|
||||
// Iteration function let's us define a generic iteration logic.
|
||||
//
|
||||
// Returns:
|
||||
// - 1 if there's a valid result
|
||||
// - 0 if reached end of the stream
|
||||
// - -1 if terminated due to until(...)
|
||||
//
|
||||
using fn_controller = std::function<int( query_desc&, iterator_type )>;
|
||||
fn_controller controller = [ ] ( auto&, auto ) { return 1; };
|
||||
|
||||
// Queries can be simply constructed from an iterator and an
|
||||
// optional direction value, where it defaults to forward iteration
|
||||
// if not .end(), backwards otherwise.
|
||||
//
|
||||
query_desc() {};
|
||||
query_desc( _iterator_type it, int8_t dir = 0 ) : iterator( it )
|
||||
{
|
||||
if ( it.is_end() && !it.is_begin() )
|
||||
direction = dir != 0 ? dir : -1;
|
||||
else
|
||||
direction = dir != 0 ? dir : +1;
|
||||
}
|
||||
|
||||
// Wraps ::recurse(...) of range iterators, returning query descriptors.
|
||||
//
|
||||
std::vector<query_desc> recurse() const
|
||||
{
|
||||
// Return an empty list if direction is invalid.
|
||||
//
|
||||
if ( direction == 0 ) return {};
|
||||
|
||||
// Get the list of possible iterators we could continue from.
|
||||
//
|
||||
std::vector iterators = iterator.recurse( direction == +1 );
|
||||
|
||||
// Convert into query descriptors.
|
||||
//
|
||||
std::vector<query_desc> query_descriptors;
|
||||
for ( iterator_type& it : iterators )
|
||||
{
|
||||
// Create a default descriptor with the iterator and the direction,
|
||||
// afterwards propagate the iteration logic.
|
||||
//
|
||||
query_desc qd = { it, direction };
|
||||
qd.controller = controller;
|
||||
query_descriptors.push_back( qd );
|
||||
}
|
||||
return query_descriptors;
|
||||
}
|
||||
|
||||
// Invalidates current query.
|
||||
//
|
||||
void stop()
|
||||
{
|
||||
iterator = prev();
|
||||
direction = 0;
|
||||
}
|
||||
|
||||
// Value that next() processed previously.
|
||||
//
|
||||
auto prev() const { return direction != +1 ? iterator : ( iterator.is_begin() ? iterator_type{} : std::prev( iterator ) ); }
|
||||
|
||||
// Value that next() will process next.
|
||||
//
|
||||
auto next() const { return direction != -1 ? iterator : ( iterator.is_begin() ? iterator_type{} : std::prev( iterator ) ); }
|
||||
|
||||
// Reverses the current query direction
|
||||
//
|
||||
void reverse()
|
||||
{
|
||||
// We have to fix the iterators since
|
||||
// .end() is valid for [-1] but not [+1]
|
||||
// and .begin() is valid for [+1] but not [-1].
|
||||
//
|
||||
if ( direction == -1 )
|
||||
{
|
||||
if ( !iterator.is_begin() ) iterator--;
|
||||
direction = +1;
|
||||
}
|
||||
else if ( direction == +1 )
|
||||
{
|
||||
if ( !iterator.is_end() ) iterator++;
|
||||
direction = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Forwards the iterator in the specified direction [n] times.
|
||||
//
|
||||
int forward( int n = 1 )
|
||||
{
|
||||
// Until we exhaust the item counter:
|
||||
//
|
||||
while ( n > 0 )
|
||||
{
|
||||
// If direction is backwards:
|
||||
//
|
||||
if ( direction == -1 )
|
||||
{
|
||||
// If we've reached .begin(), break.
|
||||
//
|
||||
if ( iterator.is_begin() )
|
||||
break;
|
||||
|
||||
// Point the iterator at the current item.
|
||||
//
|
||||
iterator--;
|
||||
|
||||
// If invalid, break.
|
||||
//
|
||||
if ( !iterator.is_valid() )
|
||||
break;
|
||||
}
|
||||
// If direction is forwards:
|
||||
//
|
||||
else if ( direction == +1 )
|
||||
{
|
||||
// If we've reached .end(), break.
|
||||
//
|
||||
if ( iterator.is_end() )
|
||||
break;
|
||||
}
|
||||
// If no direction specified, break.
|
||||
//
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Invoke the iteration logic.
|
||||
//
|
||||
int res = controller( *this, iterator );
|
||||
|
||||
// If direction was forward, increment the iterator now.
|
||||
//
|
||||
if ( direction == +1 )
|
||||
iterator++;
|
||||
|
||||
// If a breaking condition was satisfied, report so.
|
||||
//
|
||||
if ( res == -1 )
|
||||
return -1;
|
||||
|
||||
// If filters were passed and we've exhausted the
|
||||
// item counter, report success.
|
||||
//
|
||||
if ( res == 1 && --n <= 0 )
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Report end-of-stream.
|
||||
//
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
|
||||
namespace vtil::query
|
||||
{
|
||||
// Basic range iterators provide a simple range iterator
|
||||
// implementation for default STL objects and pretty much
|
||||
// any other class adhereing to their standarts, to be used
|
||||
// with VTIL queries.
|
||||
//
|
||||
template<typename container_type, typename iterator_type>
|
||||
struct range_iterator : iterator_type
|
||||
{
|
||||
using container_type = container_type;
|
||||
using iterator_type = iterator_type;
|
||||
|
||||
// Reference to the container.
|
||||
//
|
||||
container_type* container = nullptr;
|
||||
|
||||
// Default constructor and the container-bound constructor.
|
||||
//
|
||||
range_iterator() {}
|
||||
range_iterator( container_type* container, iterator_type i ) : iterator_type( i ), container( container ) {}
|
||||
template<typename X, typename Y> range_iterator( const range_iterator<X, Y>& o ) : container( o.container ), iterator_type( Y( o ) ) {}
|
||||
|
||||
// Override equality operators to check container first.
|
||||
//
|
||||
bool operator!=( const range_iterator& o ) const { return container != o.container || iterator_type::operator!=( o ); }
|
||||
bool operator==( const range_iterator& o ) const { return container == o.container && iterator_type::operator==( o ); }
|
||||
|
||||
// Simple position/validity checks.
|
||||
//
|
||||
bool is_end() const { return !container || iterator_type::operator==( ( iterator_type ) container->end() ); }
|
||||
bool is_begin() const { return !container || iterator_type::operator==( ( iterator_type ) container->begin() ); }
|
||||
bool is_valid() const { return !is_begin() || !is_end(); }
|
||||
|
||||
// No default implementation for recursion since STL has no default tree-based container.
|
||||
//
|
||||
std::vector<range_iterator> recurse( bool forward ) const { return {}; }
|
||||
};
|
||||
|
||||
// Makes range iterator from any container and iterator combination based on basic_range_iterator.
|
||||
//
|
||||
template<typename container_type>
|
||||
static auto bind( container_type& container, typename container_type::iterator iterator )
|
||||
{
|
||||
return range_iterator<container_type, typename container_type::iterator>
|
||||
{
|
||||
&container,
|
||||
iterator
|
||||
};
|
||||
}
|
||||
template<typename container_type>
|
||||
static auto bind( const container_type& container, typename container_type::const_iterator iterator )
|
||||
{
|
||||
return range_iterator<const container_type, typename container_type::const_iterator>
|
||||
{
|
||||
&container,
|
||||
iterator
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -1,356 +0,0 @@
|
|||
#pragma once
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include "view.hpp"
|
||||
#include "query_desc.hpp"
|
||||
|
||||
namespace vtil::query
|
||||
{
|
||||
// Recursive results are used to collect results
|
||||
// in a way that clearly indicates the path taken
|
||||
// to get the result, and the source container.
|
||||
//
|
||||
template<typename result_type, typename container_type>
|
||||
struct recursive_result
|
||||
{
|
||||
// Whether we've visited this container before or not.
|
||||
//
|
||||
bool is_looping = false;
|
||||
|
||||
// The container that the result belong to.
|
||||
//
|
||||
const container_type* source = nullptr;
|
||||
|
||||
// Result of the collection.
|
||||
//
|
||||
result_type result;
|
||||
|
||||
// Results of deeper recursions.
|
||||
//
|
||||
std::vector<recursive_result> paths;
|
||||
|
||||
// Merges the results of all or extended basic-blocks.
|
||||
//
|
||||
template<typename>
|
||||
struct is_std_vector : std::false_type {};
|
||||
template<typename T, typename A>
|
||||
struct is_std_vector<std::vector<T, A>> : std::true_type {};
|
||||
recursive_result& flatten( bool force = false )
|
||||
{
|
||||
// Apply to each path recursively.
|
||||
//
|
||||
for ( auto& path : paths )
|
||||
path = path.flatten();
|
||||
|
||||
// If single possible path or force mode:
|
||||
//
|
||||
if ( paths.size() == 1 || force )
|
||||
{
|
||||
std::vector paths_p = paths;
|
||||
paths.clear();
|
||||
|
||||
for ( auto& r : paths_p )
|
||||
{
|
||||
// Merge basic result.
|
||||
//
|
||||
is_looping |= r.is_looping;
|
||||
if( !r.paths.empty() )
|
||||
paths.insert( paths.end(), r.paths.begin(), r.paths.end() );
|
||||
|
||||
// Either combine vectors or use the addition operator.
|
||||
//
|
||||
if constexpr ( is_std_vector<result_type>::value )
|
||||
result.insert( result.end(), r.result.begin(), r.result.end() );
|
||||
else
|
||||
result += r.result;
|
||||
}
|
||||
}
|
||||
|
||||
// Return as is.
|
||||
//
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename view_type>
|
||||
struct recursive_view
|
||||
{
|
||||
// Base view and its typedefs.
|
||||
//
|
||||
using iterator_type = typename view_type::iterator_type;
|
||||
using projected_type = typename view_type::projected_type;
|
||||
using container_type = typename iterator_type::container_type;
|
||||
view_type view;
|
||||
|
||||
// Container filters determine whether we should
|
||||
// recurse into the passed container or not.
|
||||
//
|
||||
using fn_container_filter = std::function<bool( const container_type* src, const container_type * dst, bool first_time )>;
|
||||
fn_container_filter filter = {};
|
||||
|
||||
// Special iterator saved by the root to mark the
|
||||
// beginning of it's iteration so loops can properly
|
||||
// lead to it.
|
||||
//
|
||||
iterator_type it0 = {};
|
||||
bool it0_oob = false;
|
||||
|
||||
// List of containers that we've recursively visited.
|
||||
// The second argument of the container filter, first_time,
|
||||
// is determined by whether the container we're trying to
|
||||
// visit is in this list or not.
|
||||
//
|
||||
std::set<const void*> visited = {};
|
||||
|
||||
// Constructs a recursive view from the view structure passed.
|
||||
//
|
||||
// - If partial visits are allowed, in case of an infinite loop,
|
||||
// still iterates up to the starting point of view, first_time will
|
||||
// be set to true in this case when we reach the root container.
|
||||
//
|
||||
// - Filter is a function that takes the pointer to the next container
|
||||
// and whether it's being visit for the first time or not and returns
|
||||
// whether we should visit it or not.
|
||||
//
|
||||
//
|
||||
recursive_view() {};
|
||||
recursive_view( const view_type& view, bool partial_visits, fn_container_filter filter ) : view( view ), filter( filter )
|
||||
{
|
||||
// If partial visits are allowed:
|
||||
//
|
||||
if ( partial_visits )
|
||||
{
|
||||
// Set it0 as the next iterator, if invalid (meaning we'll skip to next
|
||||
// container right away) set the container property.
|
||||
//
|
||||
it0 = view.query.next();
|
||||
if ( !it0.is_valid() )
|
||||
{
|
||||
it0.container = view.query.iterator.container;
|
||||
it0_oob = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assing an invalid iterator to it0 and mark current
|
||||
// iterator's container visited.
|
||||
//
|
||||
it0 = {};
|
||||
visited.insert( view.query.iterator.container );
|
||||
}
|
||||
}
|
||||
|
||||
// Simply clones the current state.
|
||||
//
|
||||
recursive_view clone()
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Simple wrappers around the real view.
|
||||
// - If body only contains unreachable(), call is not valid for recursive view.
|
||||
// - Collection must not have started yet, otherwise calls are invalid.
|
||||
//
|
||||
void prev() { unreachable(); }
|
||||
void next() { unreachable(); }
|
||||
void skip( int n = 1 ) { unreachable(); }
|
||||
void last() { unreachable(); }
|
||||
|
||||
auto& reverse() { view.reverse(); return *this; }
|
||||
template<typename T> auto& run( T next ) { view.run( next ); return *this; }
|
||||
template<typename T> auto& with( T next ) { view.with( next ); return *this; }
|
||||
template<typename T> auto& where( T next ) { view.where( next ); return *this; }
|
||||
template<typename T> auto& until( T next ) { view.until( next ); return *this; }
|
||||
template<typename T> auto& whilst( T next ) { view.whilst( next ); return *this; }
|
||||
|
||||
template<typename projector_type>
|
||||
auto project( projector_type next ) { return recursive_view<decltype( view.project( next ) )>{ view.project( next ), it0.container != nullptr, filter }; }
|
||||
template<typename projector_type>
|
||||
auto reproject( projector_type next ) { return recursive_view<decltype( view.reproject( next ) )>{ view.reproject( next ), it0.container != nullptr, filter }; }
|
||||
auto unproject() { return recursive_view<decltype( view.unproject() )>{ view.unproject(), it0.container != nullptr, filter }; }
|
||||
|
||||
// [Collection method]
|
||||
// Invokes the enumerator for each entry, if enumerator returns void/bool
|
||||
// saves the number of (?=true) entries, otherwise collects the return value
|
||||
// in std::vector<> and saves that in the recursive_result structure.
|
||||
// Continues appending paths and results in that structure until
|
||||
// stream is finished.
|
||||
//
|
||||
template<typename enumerator_type,
|
||||
typename return_type = decltype( std::declval<enumerator_type>()( std::declval<projected_type>() ) ),
|
||||
typename result_type = std::conditional_t<std::is_same_v<return_type, void>, size_t, std::vector<return_type>>
|
||||
>
|
||||
recursive_result<result_type, container_type> for_each( const enumerator_type& enumerator )
|
||||
{
|
||||
// Begin the iteration loop.
|
||||
//
|
||||
recursive_result<result_type, container_type> output = { false, view.query.iterator.container, {}, {} };
|
||||
while ( true )
|
||||
{
|
||||
int r = view.query.forward();
|
||||
|
||||
// If a breaking condition was satisfied, end the loop.
|
||||
//
|
||||
if ( r == -1 )
|
||||
{
|
||||
break;
|
||||
}
|
||||
// If entry passed the filters, append the result and continue.
|
||||
//
|
||||
else if ( r == 1 )
|
||||
{
|
||||
if constexpr ( std::is_same_v<return_type, void> )
|
||||
output.result++, enumerator( view.prev() );
|
||||
else if constexpr ( std::is_same_v<return_type, bool> )
|
||||
output.result += enumerator( view.prev() );
|
||||
else
|
||||
output.result.push_back( enumerator( view.prev() ) );
|
||||
}
|
||||
// If we've reached the end of the stream, try recursing.
|
||||
//
|
||||
else
|
||||
{
|
||||
// For each plausible path:
|
||||
//
|
||||
std::vector desc_list = view.query.recurse();
|
||||
for ( auto& desc : desc_list )
|
||||
{
|
||||
auto visited_copy = visited;
|
||||
|
||||
// If we did not already visit it:
|
||||
//
|
||||
bool first_visit = visited_copy.find( desc.iterator.container ) == visited_copy.end();
|
||||
if ( filter( view.query.iterator.container, desc.iterator.container, first_visit ) )
|
||||
{
|
||||
// Mark the container visited.
|
||||
//
|
||||
visited_copy.insert( desc.iterator.container );
|
||||
|
||||
// Create another recursive view with the new query.
|
||||
//
|
||||
recursive_view view_new = clone();
|
||||
view_new.view.query = desc;
|
||||
view_new.visited = visited_copy;
|
||||
|
||||
// If iterator belongs to the same container as the root:
|
||||
//
|
||||
bool partial_loop = desc.iterator.container == it0.container;
|
||||
if ( partial_loop )
|
||||
{
|
||||
// Append an additional rule to the iteration.
|
||||
//
|
||||
if( !it0_oob )
|
||||
view_new.view = view_new.view.until( it0 );
|
||||
}
|
||||
|
||||
// Invoke the same enumerator and append as a path.
|
||||
//
|
||||
recursive_result<result_type, container_type> result = view_new.for_each<enumerator_type, return_type, result_type>( enumerator );
|
||||
result.is_looping = partial_loop;
|
||||
output.paths.push_back( result );
|
||||
}
|
||||
// Otherwise:
|
||||
//
|
||||
else
|
||||
{
|
||||
// Append an empty path marked as a loop.
|
||||
//
|
||||
output.paths.push_back( { true, view.query.iterator.container, {}, {} } );
|
||||
}
|
||||
}
|
||||
|
||||
// Break out.
|
||||
//
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the final result.
|
||||
//
|
||||
return output;
|
||||
}
|
||||
|
||||
// [Collection method]
|
||||
// Collects each entry in std::vector<> and saves that in the
|
||||
// recursive_result structure. Continues appending paths and
|
||||
// results in that structure until stream is finished.
|
||||
//
|
||||
auto collect()
|
||||
{
|
||||
return for_each( [ ] ( projected_type r ) { return r; } );
|
||||
}
|
||||
|
||||
// [Collection method]
|
||||
// Evaluates the iteration logic and returns the number of hits
|
||||
// in terms of recursive_result.
|
||||
//
|
||||
auto evaluate()
|
||||
{
|
||||
return for_each( [ ] ( projected_type r ) {} );
|
||||
}
|
||||
|
||||
// [Collection method]
|
||||
// Collects first entry in std::vector<>, saves that in the
|
||||
// recursive_result structure and stops if applicable.
|
||||
// Otherwise continues appending paths in that structure
|
||||
// until a valid entry is hit.
|
||||
//
|
||||
auto first()
|
||||
{
|
||||
auto prev = view.query.controller;
|
||||
view.query.controller = [ prev ] ( auto& self, iterator_type i ) -> int
|
||||
{
|
||||
// If current iterator reports end or filtered-out,
|
||||
// return as is.
|
||||
//
|
||||
int res = prev( self, i );
|
||||
if ( res <= 0 )
|
||||
return res;
|
||||
|
||||
// Else, stop the query and return it to be processed.
|
||||
//
|
||||
self.stop();
|
||||
return 1;
|
||||
};
|
||||
return collect();
|
||||
}
|
||||
};
|
||||
|
||||
// Converts the view to a recursive view. Since recursive view only contains collection
|
||||
// methods, all filtering/projection/iteration-logic should be processed at the base view.
|
||||
//
|
||||
template<typename view_type>
|
||||
static auto recurse( view_type view, typename recursive_view<view_type>::fn_container_filter filter = {}, bool partial_visits = true, bool safe = true )
|
||||
{
|
||||
return recursive_view<view_type>( view, partial_visits, [ filter, safe ] ( auto* src, auto* dst, bool first_time )
|
||||
{
|
||||
return ( !safe || first_time ) && ( !filter || filter( src, dst, true ) );
|
||||
} );
|
||||
}
|
||||
|
||||
// Creates a reference-view query for the given query base.
|
||||
//
|
||||
template<typename iterator_type,
|
||||
typename view_type = view<typename query_desc<iterator_type>::reference_type, query_desc<iterator_type>>
|
||||
>
|
||||
static auto create_recursive( query_desc<iterator_type> q, typename recursive_view<view_type>::fn_container_filter filter = {}, bool partial_visits = true, bool safe = true )
|
||||
{
|
||||
return recurse<view_type>
|
||||
(
|
||||
view_type( q ),
|
||||
filter,
|
||||
partial_visits,
|
||||
safe
|
||||
);
|
||||
}
|
||||
|
||||
// Creates a reference-view query for the given range iterator.
|
||||
//
|
||||
template<typename iterator_type,
|
||||
typename view_type = view<typename query_desc<iterator_type>::reference_type, query_desc<iterator_type>>
|
||||
>
|
||||
static auto create_recursive( iterator_type r, int8_t dir = 0, typename recursive_view<view_type>::fn_container_filter filter = {}, bool partial_visits = true, bool safe = true )
|
||||
{
|
||||
return create_recursive( query_desc{ r, dir }, filter, partial_visits, safe );
|
||||
}
|
||||
};
|
||||
|
|
@ -1,444 +0,0 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include "query_desc.hpp"
|
||||
|
||||
namespace vtil::query
|
||||
{
|
||||
// Query views provide the user with an interface interact
|
||||
// with any query in a simple fashion using a projected type
|
||||
// of their own choice.
|
||||
//
|
||||
template<typename _projected_type, typename query_desc>
|
||||
struct view
|
||||
{
|
||||
using projected_type = _projected_type;
|
||||
|
||||
// Base query and its typedefs.
|
||||
//
|
||||
query_desc query;
|
||||
using fn_controller = typename query_desc::fn_controller;
|
||||
using iterator_type = typename query_desc::iterator_type;
|
||||
using reference_type = typename query_desc::reference_type;
|
||||
|
||||
// Projectors convert the iterator into a user-defined format
|
||||
// the invoker will be using.
|
||||
//
|
||||
using fn_projector = std::function<projected_type( query_desc&, iterator_type )>;
|
||||
fn_projector project_value;
|
||||
|
||||
// Generic callback wrapper used so that expressions accepting the
|
||||
// projected type as an argument and expressions accepting the base
|
||||
// iterator type as an argument can be passed via the same function.
|
||||
//
|
||||
template<typename callback_type>
|
||||
struct callback_wrapper
|
||||
{
|
||||
// Storage of the callback in it's original type.
|
||||
//
|
||||
callback_type callback_stored;
|
||||
callback_wrapper( callback_type cb ) : callback_stored( cb ) {}
|
||||
|
||||
// This definition will be used for callbacks that can accept the
|
||||
// projected value over the iterator type where possible as it's the
|
||||
// prefered type.
|
||||
//
|
||||
template<typename fn_projector_i, typename = decltype( std::declval<callback_type>()( std::declval<projected_type>() ) )>
|
||||
auto invoke( iterator_type it, query_desc& desc, fn_projector_i project, bool ) const
|
||||
{
|
||||
return callback_stored( project( desc, it ) );
|
||||
}
|
||||
|
||||
// This definition will be used for callbacks that can only work with the
|
||||
// iterator type and not the projected type. This is not the prefered method
|
||||
// and that's an important distinction to make as in the situation of projected type
|
||||
// being std::next(it) for instance, this method being prefered would make it so that
|
||||
// the projector is ignored. That is not the case thanks to this method being the second
|
||||
// option.
|
||||
//
|
||||
template<typename fn_projector_i>
|
||||
auto invoke( iterator_type it, query_desc& desc, fn_projector_i project, ... ) const
|
||||
{
|
||||
return callback_stored( it );
|
||||
}
|
||||
|
||||
// Calls into invoke with a boolean and picks whichever possible.
|
||||
//
|
||||
template<typename fn_projector_i>
|
||||
auto operator()( iterator_type it, query_desc& desc, fn_projector_i project ) const
|
||||
{
|
||||
return invoke( it, desc, project, true );
|
||||
}
|
||||
};
|
||||
|
||||
// Constructor takes the query descriptor and a projector.
|
||||
//
|
||||
view( query_desc desc, fn_projector projector = {} ) : project_value( projector ), query( desc )
|
||||
{
|
||||
if ( !project_value )
|
||||
{
|
||||
if constexpr ( std::is_same_v<projected_type, reference_type> )
|
||||
project_value = [ ] ( auto&, iterator_type i ) -> projected_type { return *i; };
|
||||
else if constexpr ( std::is_same_v<iterator_type, projected_type> )
|
||||
project_value = [ ] ( auto&, iterator_type i ) -> projected_type { return i; };
|
||||
else
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
// Simply clones the current state of the view.
|
||||
//
|
||||
view clone()
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
// These provide a simple wrapper around the query descrtiptor's
|
||||
// ::prev() and ::next() returning the projected value instead.
|
||||
//
|
||||
projected_type prev() { return project_value( query, query.prev() ); }
|
||||
projected_type next() { return project_value( query, query.next() ); }
|
||||
|
||||
// Reverses the query descriptor's direction.
|
||||
//
|
||||
auto& reverse()
|
||||
{
|
||||
query.reverse();
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Skips the [n] valid entries.
|
||||
//
|
||||
auto& skip( int n = 1 )
|
||||
{
|
||||
query.forward( n );
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Returns the current controller.
|
||||
// This function can be used to implement query extensions
|
||||
// where one just passes the summarized controller of a dummy view
|
||||
// as an argument to a routine that creates the view after which the
|
||||
// routine invokes ::control( arg ) on the real view.
|
||||
//
|
||||
fn_controller to_controller()
|
||||
{
|
||||
return query.controller;
|
||||
}
|
||||
|
||||
// [Projection method]
|
||||
// Projects the current result type as specified by the projector and
|
||||
// returns a new query view of that type.
|
||||
//
|
||||
template<typename projector_type>
|
||||
auto project( projector_type next )
|
||||
{
|
||||
// Find out the new projected type
|
||||
//
|
||||
using projected_type_n = decltype( next( std::declval<projected_type>() ) );
|
||||
|
||||
// Save previous projector and create the new view
|
||||
//
|
||||
fn_projector prev = project_value;
|
||||
return view<projected_type_n, query_desc>
|
||||
{
|
||||
query,
|
||||
[ prev, next ] ( query_desc& self, iterator_type i ) -> projected_type_n
|
||||
{
|
||||
return next( prev( self, i ) );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// [Projection method]
|
||||
// Reverts the projected type to the iterator of the entry.
|
||||
//
|
||||
auto unproject()
|
||||
{
|
||||
return view<iterator_type, query_desc>{ query };
|
||||
}
|
||||
|
||||
// [Projection method]
|
||||
// Projects the iterator type as specified by the projector and
|
||||
// returns a new query view of that type. (Equivalent to the
|
||||
// combination of unproject + project)
|
||||
//
|
||||
template<typename projector_type>
|
||||
auto reproject( projector_type next )
|
||||
{
|
||||
// Find out the new projected type
|
||||
//
|
||||
using projected_type_n = decltype( next( std::declval<iterator_type>() ) );
|
||||
|
||||
// Save previous projector and create the new view
|
||||
//
|
||||
return view<projected_type_n, query_desc>
|
||||
{
|
||||
query,
|
||||
[ next ] ( query_desc& self, iterator_type i ) -> projected_type_n
|
||||
{
|
||||
return next( i );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// [Combination method]
|
||||
// For each entry, invokes the controller and returns the integer
|
||||
// it returns as is. For more details on what this value
|
||||
// represents, read the note for query_base::fn_controller and
|
||||
// ::to_controller()
|
||||
//
|
||||
auto& with( fn_controller controller )
|
||||
{
|
||||
// Override iteration logic.
|
||||
//
|
||||
fn_controller prev = query.controller;
|
||||
query.controller = [ prev, controller ] ( query_desc& self, iterator_type i ) -> int
|
||||
{
|
||||
// If current iterator reports end or filtered-out,
|
||||
// return as is.
|
||||
//
|
||||
int res = prev( self, i );
|
||||
if ( res <= 0 )
|
||||
return res;
|
||||
// Else, run our additional layer of logic.
|
||||
//
|
||||
return controller( self, i );
|
||||
};
|
||||
return *this;
|
||||
}
|
||||
|
||||
// [Filtering method]
|
||||
// For each entry, invokes the filter and if it returns false,
|
||||
// skips it and continues from the next one.
|
||||
//
|
||||
template<typename callback_type>
|
||||
auto& where( callback_type cb )
|
||||
{
|
||||
// Allow callbacks taking non-projected type if they take
|
||||
// the base iterator type.
|
||||
//
|
||||
fn_projector project = project_value;
|
||||
callback_wrapper<callback_type> callback = { cb };
|
||||
|
||||
// Override iteration logic.
|
||||
//
|
||||
fn_controller prev = query.controller;
|
||||
query.controller = [ prev, project, callback ] ( query_desc& self, iterator_type i ) -> int
|
||||
{
|
||||
// If current iterator reports end or filtered-out,
|
||||
// return as is.
|
||||
//
|
||||
int res = prev( self, i );
|
||||
if ( res <= 0 )
|
||||
return res;
|
||||
// Else, run logic for our additional layer of filtering.
|
||||
//
|
||||
return callback( i, self, project ) ? 1 : 0;
|
||||
};
|
||||
return *this;
|
||||
}
|
||||
|
||||
// [Filtering method]
|
||||
// For each entry, invokes the filter and if it returns true,
|
||||
/// breaks out of the loop. If user provides an iterator instead
|
||||
// of a callback function, it breaks out of iteration when it
|
||||
// reaches that iterator instead.
|
||||
//
|
||||
template<typename argument_type>
|
||||
auto& until( argument_type arg )
|
||||
{
|
||||
if constexpr ( !std::is_same_v<std::remove_cvref_t<argument_type>, iterator_type> )
|
||||
{
|
||||
// Allow callbacks taking non-projected type if they take
|
||||
// the base iterator type.
|
||||
//
|
||||
fn_projector project = project_value;
|
||||
callback_wrapper<argument_type> callback = { arg };
|
||||
|
||||
// Override iteration logic.
|
||||
//
|
||||
fn_controller prev = query.controller;
|
||||
query.controller = [ prev, project, callback ] ( query_desc& self, iterator_type i ) -> int
|
||||
{
|
||||
// If current iterator reports end or filtered-out,
|
||||
// return as is.
|
||||
//
|
||||
int res = prev( self, i );
|
||||
if ( res <= 0 )
|
||||
return res;
|
||||
// Else, run logic for our additional layer of filtering.
|
||||
//
|
||||
return callback( i, self, project ) ? -1 : 1;
|
||||
};
|
||||
return *this;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Override iteration logic.
|
||||
//
|
||||
fn_controller prev = query.controller;
|
||||
iterator_type stop_at = arg;
|
||||
query.controller = [ prev, stop_at ] ( query_desc& self, iterator_type i ) -> int
|
||||
{
|
||||
// Break if we reached the target iterator.
|
||||
//
|
||||
return i == stop_at ? -1 : prev( self, i );
|
||||
};
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
|
||||
// [Filtering method]
|
||||
// For each entry, invokes the filter and if it returns false,
|
||||
/// breaks out of the loop.
|
||||
//
|
||||
template<typename callback_type>
|
||||
auto& whilst( callback_type cb )
|
||||
{
|
||||
// Allow callbacks taking non-projected type if they take
|
||||
// the base iterator type.
|
||||
//
|
||||
fn_projector project = project_value;
|
||||
callback_wrapper<callback_type> callback = { cb };
|
||||
|
||||
// Override iteration logic.
|
||||
//
|
||||
fn_controller prev = query.controller;
|
||||
query.controller = [ prev, project, callback ] ( query_desc& self, iterator_type i ) -> int
|
||||
{
|
||||
// If current iterator reports end or filtered-out,
|
||||
// return as is.
|
||||
//
|
||||
int res = prev( self, i );
|
||||
if ( res <= 0 )
|
||||
return res;
|
||||
// Else, run logic for our additional layer of filtering.
|
||||
//
|
||||
return callback( i, self, project ) ? 1 : -1;
|
||||
};
|
||||
return *this;
|
||||
}
|
||||
|
||||
// [Filtering method]
|
||||
// For each entry valid in the current(!) conditions, invokes
|
||||
// the given enumerator function.
|
||||
//
|
||||
template<typename callback_type>
|
||||
auto& run( callback_type cb )
|
||||
{
|
||||
if ( !next ) return *this;
|
||||
|
||||
// Allow callbacks taking non-projected type if they take
|
||||
// the base iterator type.
|
||||
//
|
||||
fn_projector project = project_value;
|
||||
callback_wrapper<callback_type> callback = { cb };
|
||||
|
||||
// Override iteration logic.
|
||||
//
|
||||
fn_controller prev = query.controller;
|
||||
query.controller = [ prev, project, callback ] ( query_desc& self, iterator_type i ) -> int
|
||||
{
|
||||
// If current iterator reports end or filtered-out,
|
||||
// return as is.
|
||||
//
|
||||
int res = prev( self, i );
|
||||
if ( res <= 0 )
|
||||
return res;
|
||||
// Else, invoke the enumerator and continue.
|
||||
//
|
||||
callback( i, self, project );
|
||||
return 1;
|
||||
};
|
||||
return *this;
|
||||
}
|
||||
|
||||
// [Collection method]
|
||||
// Invokes the enumerator for each entry, if enumerator returns void/bool
|
||||
// returns the number of (?=true) entries, otherwise collects the return value
|
||||
// in std::vector<> and returns that.
|
||||
//
|
||||
template<typename enumerator_type>
|
||||
auto for_each( const enumerator_type& enumerator )
|
||||
{
|
||||
using T = decltype( enumerator( std::declval<projected_type>() ) );
|
||||
|
||||
if constexpr ( std::is_same_v<T, void> )
|
||||
{
|
||||
size_t count = 0;
|
||||
while ( query.forward() == 1 )
|
||||
count++, enumerator( prev() );
|
||||
return count;
|
||||
}
|
||||
else if constexpr ( std::is_same_v<T, bool> )
|
||||
{
|
||||
size_t count = 0;
|
||||
while ( query.forward() == 1 )
|
||||
count += enumerator( prev() );
|
||||
return count;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<T> result;
|
||||
while ( query.forward() == 1 )
|
||||
result.push_back( enumerator( prev() ) );
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// [Collection method]
|
||||
// Collects all entries in a vector and returns it as is.
|
||||
//
|
||||
std::vector<projected_type> collect()
|
||||
{
|
||||
return for_each( [ ] ( projected_type r ) { return r; } );
|
||||
}
|
||||
|
||||
// [Collection method]
|
||||
// Evaluates the iteration logic and returns the number of hits.
|
||||
//
|
||||
auto evaluate()
|
||||
{
|
||||
return for_each( [ ] ( projected_type r ) {} );
|
||||
}
|
||||
|
||||
// [Collection method]
|
||||
// Returns the first valid entry in the stream, nullopt if there were none.
|
||||
//
|
||||
std::optional<projected_type> first()
|
||||
{
|
||||
if ( query.forward() == 1 )
|
||||
return prev();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// [Collection method]
|
||||
// Returns the last valid entry in the stream, nullopt if there were none.
|
||||
//
|
||||
std::optional<projected_type> last()
|
||||
{
|
||||
std::optional<projected_type> res;
|
||||
while ( query.forward() == 1 )
|
||||
res = prev();
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
// Creates a reference-view query for the given query base.
|
||||
//
|
||||
template<typename iterator_type>
|
||||
static auto create( query_desc<iterator_type> q ) { return view<typename query_desc<iterator_type>::reference_type, query_desc<iterator_type>>( q ); }
|
||||
|
||||
// Creates a reference-view query for the given range iterator.
|
||||
//
|
||||
template<typename iterator_type>
|
||||
static auto create( iterator_type r, int8_t dir = 0 ) { return create( query_desc{ r, dir } ); }
|
||||
|
||||
// Creates a dummy view that can be used to extract the summarized control logic.
|
||||
//
|
||||
template<typename iterator_type>
|
||||
static auto dummy() { return create( query_desc<iterator_type>{} ); }
|
||||
};
|
||||
281
VTIL-Architecture/routine/basic_block.cpp
Normal file
281
VTIL-Architecture/routine/basic_block.cpp
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#include "basic_block.hpp"
|
||||
|
||||
namespace vtil
|
||||
{
|
||||
// Constructor does not exist. Should be created either using
|
||||
// ::begin(...) or ->fork(...).
|
||||
//
|
||||
basic_block* basic_block::begin( vip_t entry_vip )
|
||||
{
|
||||
// Caller must provide a valid virtual instruction pointer.
|
||||
//
|
||||
fassert( entry_vip != invalid_vip );
|
||||
|
||||
// Create the basic block with depth = 0, identifier = "0"
|
||||
//
|
||||
basic_block* blk = new basic_block;
|
||||
blk->entry_vip = entry_vip;
|
||||
|
||||
// Create the routine and assign this block as the entry-point
|
||||
//
|
||||
blk->owner = new routine;
|
||||
blk->owner->entry_point = blk;
|
||||
blk->owner->explored_blocks[ entry_vip ] = blk;
|
||||
|
||||
// Return the block
|
||||
//
|
||||
return blk;
|
||||
}
|
||||
basic_block* basic_block::fork( vip_t entry_vip )
|
||||
{
|
||||
|
||||
// Block cannot be forked before a branching instruction is hit.
|
||||
//
|
||||
fassert( is_complete() );
|
||||
|
||||
// Caller must provide a valid virtual instruction pointer.
|
||||
//
|
||||
fassert( entry_vip != invalid_vip );
|
||||
|
||||
// Check if the routine has already explored this block.
|
||||
//
|
||||
std::lock_guard g( owner->mutex );
|
||||
basic_block* result = nullptr;
|
||||
basic_block*& entry = owner->explored_blocks[ entry_vip ];
|
||||
if ( !entry )
|
||||
{
|
||||
// If it did not, create a block and assign it.
|
||||
//
|
||||
result = new basic_block;
|
||||
result->owner = owner;
|
||||
result->entry_vip = entry_vip;
|
||||
result->sp_offset = 0;
|
||||
entry = result;
|
||||
}
|
||||
|
||||
// Fix the links and quit the scope holding the lock.
|
||||
//
|
||||
next.push_back( entry );
|
||||
entry->prev.push_back( this );
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helpers for the allocation of unique temporary registers
|
||||
//
|
||||
arch::register_view basic_block::tmp( uint8_t size )
|
||||
{
|
||||
return arch::register_view
|
||||
{
|
||||
"t" + std::to_string( ++owner->temporary_index_counter ),
|
||||
0,
|
||||
size
|
||||
};
|
||||
}
|
||||
|
||||
// Instruction pre-processor
|
||||
//
|
||||
void basic_block::append_instruction( instruction ins )
|
||||
{
|
||||
// Cannot access EFLAGS directly.
|
||||
//
|
||||
static const auto EFLAGS_MAPPINGS = { std::pair{ REG_OF, X86_EFLAGS_OF_BIT },
|
||||
std::pair{ REG_SF, X86_EFLAGS_SF_BIT },
|
||||
std::pair{ REG_ZF, X86_EFLAGS_ZF_BIT },
|
||||
std::pair{ REG_AF, X86_EFLAGS_AF_BIT },
|
||||
std::pair{ REG_PF, X86_EFLAGS_PF_BIT } };
|
||||
|
||||
if ( int w_op = ins.writes_to( X86_REG_EFLAGS ) )
|
||||
{
|
||||
auto t1 = tmp( 8 );
|
||||
mov( t1, 0ull );
|
||||
ins.operands[ w_op - 1 ].reg.base = t1.base;
|
||||
|
||||
append_instruction( ins );
|
||||
|
||||
for ( auto& pair : EFLAGS_MAPPINGS )
|
||||
{
|
||||
auto t0 = tmp( 8 );
|
||||
mov( t0, t1 );
|
||||
bshr( t0, pair.second );
|
||||
band( t0, 1 );
|
||||
mov( pair.first, t0 )->stream.back().make_volatile();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ( int r_op = ins.reads_from( X86_REG_EFLAGS ) )
|
||||
{
|
||||
auto t1 = tmp( 8 );
|
||||
mov( t1, 0ull );
|
||||
ins.operands[ r_op - 1 ].reg.base = t1.base;
|
||||
|
||||
for ( auto& pair : EFLAGS_MAPPINGS )
|
||||
{
|
||||
auto t0 = tmp( 8 );
|
||||
mov( t0, pair.first );
|
||||
bshl( t0, pair.second );
|
||||
band( t0, 1ull << pair.second );
|
||||
bor( t1, t0 );
|
||||
}
|
||||
}
|
||||
|
||||
fassert( !ins.writes_to( X86_REG_EFLAGS ) && !ins.reads_from( X86_REG_EFLAGS ) );
|
||||
|
||||
// Instructions cannot be appended after a branching instruction was hit.
|
||||
//
|
||||
fassert( !is_complete() );
|
||||
|
||||
// Write the stack pointer details.
|
||||
//
|
||||
ins.sp_offset = sp_offset;
|
||||
ins.sp_index = sp_index;
|
||||
|
||||
// If instruction writes to RSP, reset the queued stack pointer.
|
||||
//
|
||||
if ( ins.writes_to( X86_REG_RSP ) )
|
||||
{
|
||||
sp_offset = 0;
|
||||
sp_index++;
|
||||
ins.sp_reset = true;
|
||||
}
|
||||
|
||||
// Append the instruction to the stream.
|
||||
//
|
||||
stream.push_back( ins );
|
||||
}
|
||||
|
||||
// Updates EFLAGS register based on the previous instruction executed and writes it
|
||||
// into the operand given.
|
||||
//
|
||||
basic_block* basic_block::uflags_result( operand op, bool carry )
|
||||
{
|
||||
// SF, ZF, PF will be set according to the result.
|
||||
//
|
||||
//
|
||||
// AF will be set to undefined, as they are not implemented yet.
|
||||
// OF, CF will be cleared.
|
||||
|
||||
this->setz( REG_ZF, op )
|
||||
->sets( REG_SF, op )
|
||||
->setp( REG_PF, op )
|
||||
->mov( REG_AF, REG_UNKB );
|
||||
if ( carry )
|
||||
seto( REG_OF, op )->setc( REG_CF, op );
|
||||
else
|
||||
mov( REG_OF, 0ull )->mov( REG_CF, 0ull );
|
||||
return this;
|
||||
}
|
||||
|
||||
// Queues a stack shift.
|
||||
//
|
||||
basic_block* basic_block::shift_sp( int64_t offset, bool merge_instance, iterator it )
|
||||
{
|
||||
// If requested, shift the stack index first.
|
||||
//
|
||||
if ( merge_instance )
|
||||
{
|
||||
// Assert instruction at iterator indeed resets stack pointer.
|
||||
//
|
||||
fassert( !it.is_end() && it->sp_reset );
|
||||
|
||||
// Decrement stack index for each instruction afterwards.
|
||||
//
|
||||
for ( auto i = std::next( it ); !i.is_end(); i++ )
|
||||
i->sp_index--;
|
||||
sp_index--;
|
||||
|
||||
// Remove the reset flag and merge the offsets.
|
||||
//
|
||||
it->sp_reset = false;
|
||||
offset += it->sp_offset;
|
||||
it->sp_offset = 0;
|
||||
}
|
||||
|
||||
// If an iterator is provided, shift the stack pointer
|
||||
// for every instruction that precedes it as well.
|
||||
//
|
||||
std::optional<uint32_t> sp_index_prev;
|
||||
while ( !it.is_end() )
|
||||
{
|
||||
// Shift the stack offset accordingly.
|
||||
//
|
||||
it->sp_offset += offset;
|
||||
|
||||
// If instruction reads from RSP:
|
||||
//
|
||||
if ( it->reads_from( X86_REG_RSP ) )
|
||||
{
|
||||
// If LDR|STR with memory operand RSP:
|
||||
//
|
||||
if ( it->base->accesses_memory() && it->operands[ it->base->memory_operand_index ].reg == X86_REG_RSP )
|
||||
{
|
||||
// Assert the offset operand is an immediate and
|
||||
// shift the offset as well.
|
||||
//
|
||||
fassert( it->operands[ it->base->memory_operand_index + 1 ].is_immediate() );
|
||||
it->operands[ it->base->memory_operand_index + 1 ].i64 += offset;
|
||||
}
|
||||
}
|
||||
|
||||
// If stack changed changed, return, else forward the iterator.
|
||||
//
|
||||
if ( sp_index_prev.value_or( it->sp_index ) != it->sp_index )
|
||||
return this;
|
||||
sp_index_prev = it->sp_index;
|
||||
++it;
|
||||
}
|
||||
|
||||
// Shift the stack pointer and continue as usual
|
||||
// without emitting any sub or add instructions.
|
||||
// Queued stack pointer changes will be processed
|
||||
// in bulk at the end of the routine.
|
||||
//
|
||||
sp_offset += offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Pushes current flags value up the stack queueing the
|
||||
// shift in stack pointer.
|
||||
//
|
||||
basic_block* basic_block::pushf()
|
||||
{
|
||||
return push( X86_REG_EFLAGS );
|
||||
}
|
||||
|
||||
// Emits an entire instruction using series of VEMITs.
|
||||
//
|
||||
basic_block* basic_block::vemits( const std::string& assembly )
|
||||
{
|
||||
auto res = keystone::assemble( assembly );
|
||||
fassert( !res.empty() );
|
||||
for ( uint8_t byte : res )
|
||||
vemit( byte );
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,10 +1,37 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#pragma once
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <keystone.hpp>
|
||||
#include <vtil/amd64>
|
||||
#include "routine.hpp"
|
||||
#include "instruction.hpp"
|
||||
|
||||
|
|
@ -20,9 +47,8 @@ namespace vtil
|
|||
// expression simplification in order to resolve branch destinations
|
||||
// or stack pointer value when required.
|
||||
//
|
||||
// - No block should under any
|
||||
// circumstance modify any of the properties of any other block,
|
||||
// with the only exception being .prev.
|
||||
// - No block should under any circumstance modify any of the properties
|
||||
// of any other block, with the only exception being .prev.
|
||||
//
|
||||
struct basic_block
|
||||
{
|
||||
|
|
@ -45,7 +71,7 @@ namespace vtil
|
|||
|
||||
// Default constructor and the block-bound constructor.
|
||||
//
|
||||
riterator_base() {}
|
||||
riterator_base() = default;
|
||||
riterator_base( container_type* ref, const iterator_type& i ): container( ref ), iterator_type( i ) {}
|
||||
template<typename X, typename Y> riterator_base( const riterator_base<X, Y>& o ) : container( o.container ), iterator_type( Y( o ) ) {}
|
||||
|
||||
|
|
@ -190,90 +216,26 @@ namespace vtil
|
|||
|
||||
// Wrap the std::list fundamentals.
|
||||
//
|
||||
auto size() const { return stream.size(); }
|
||||
iterator end() { return { this, stream.end() }; }
|
||||
iterator begin() { return { this, stream.begin() }; }
|
||||
const_iterator end() const { return { this, stream.end() }; }
|
||||
const_iterator begin() const { return { this, stream.begin() }; }
|
||||
inline auto size() const { return stream.size(); }
|
||||
inline iterator end() { return { this, stream.end() }; }
|
||||
inline iterator begin() { return { this, stream.begin() }; }
|
||||
inline const_iterator end() const { return { this, stream.end() }; }
|
||||
inline const_iterator begin() const { return { this, stream.begin() }; }
|
||||
|
||||
// Returns whether or not stream is complete.
|
||||
// Returns whether or not block is complete, a complete
|
||||
// block ends with a branching instruction.
|
||||
//
|
||||
bool is_complete() const
|
||||
{
|
||||
// Instructions cannot be appended after a branching instruction was hit.
|
||||
//
|
||||
return !stream.empty() && stream.back().base->is_branching();
|
||||
}
|
||||
inline bool is_complete() const { return !stream.empty() && stream.back().base->is_branching(); }
|
||||
|
||||
// Constructor does not exist. Should be created either using
|
||||
// ::begin(...) or ->fork(...).
|
||||
//
|
||||
static basic_block* begin( vip_t entry_vip )
|
||||
{
|
||||
// Caller must provide a valid virtual instruction pointer.
|
||||
//
|
||||
fassert( entry_vip != invalid_vip );
|
||||
|
||||
// Create the basic block with depth = 0, identifier = "0"
|
||||
//
|
||||
basic_block* blk = new basic_block;
|
||||
blk->entry_vip = entry_vip;
|
||||
|
||||
// Create the routine and assign this block as the entry-point
|
||||
//
|
||||
blk->owner = new routine;
|
||||
blk->owner->entry_point = blk;
|
||||
blk->owner->explored_blocks[ entry_vip ] = blk;
|
||||
|
||||
// Return the block
|
||||
//
|
||||
return blk;
|
||||
}
|
||||
basic_block* fork( vip_t entry_vip )
|
||||
{
|
||||
|
||||
// Block cannot be forked before a branching instruction is hit.
|
||||
//
|
||||
fassert( is_complete() );
|
||||
|
||||
// Caller must provide a valid virtual instruction pointer.
|
||||
//
|
||||
fassert( entry_vip != invalid_vip );
|
||||
|
||||
// Check if the routine has already explored this block.
|
||||
//
|
||||
std::lock_guard g( owner->mutex );
|
||||
basic_block* result = nullptr;
|
||||
basic_block*& entry = owner->explored_blocks[ entry_vip ];
|
||||
if ( !entry )
|
||||
{
|
||||
// If it did not, create a block and assign it.
|
||||
//
|
||||
result = new basic_block;
|
||||
result->owner = owner;
|
||||
result->entry_vip = entry_vip;
|
||||
result->sp_offset = 0;
|
||||
entry = result;
|
||||
}
|
||||
|
||||
// Fix the links and quit the scope holding the lock.
|
||||
//
|
||||
next.push_back( entry );
|
||||
entry->prev.push_back( this );
|
||||
return result;
|
||||
}
|
||||
static basic_block* begin( vip_t entry_vip );
|
||||
basic_block* fork( vip_t entry_vip );
|
||||
|
||||
// Helpers for the allocation of unique temporary registers
|
||||
//
|
||||
auto tmp( uint8_t size )
|
||||
{
|
||||
return arch::register_view
|
||||
{
|
||||
"t" + std::to_string( ++owner->temporary_index_counter ),
|
||||
0,
|
||||
size
|
||||
};
|
||||
}
|
||||
arch::register_view tmp( uint8_t size );
|
||||
template<typename... params>
|
||||
auto tmp( uint8_t size_0, params... size_n )
|
||||
{
|
||||
|
|
@ -282,30 +244,7 @@ namespace vtil
|
|||
|
||||
// Instruction pre-processor
|
||||
//
|
||||
void append_instruction( instruction ins )
|
||||
{
|
||||
// Instructions cannot be appended after a branching instruction was hit.
|
||||
//
|
||||
fassert( !is_complete() );
|
||||
|
||||
// Write the stack pointer details.
|
||||
//
|
||||
ins.sp_offset = sp_offset;
|
||||
ins.sp_index = sp_index;
|
||||
|
||||
// If instruction writes to RSP, reset the queued stack pointer.
|
||||
//
|
||||
if ( ins.writes_to( X86_REG_RSP ) )
|
||||
{
|
||||
sp_offset = 0;
|
||||
sp_index++;
|
||||
ins.sp_reset = true;
|
||||
}
|
||||
|
||||
// Append the instruction to the stream.
|
||||
//
|
||||
stream.push_back( ins );
|
||||
}
|
||||
void append_instruction( instruction ins );
|
||||
|
||||
// Lazy wrappers for every instruction
|
||||
//
|
||||
|
|
@ -317,18 +256,21 @@ namespace vtil
|
|||
if constexpr ( std::is_same_v<T, register_view> ||
|
||||
std::is_same_v<T, operand> )
|
||||
return operand( value );
|
||||
|
||||
// If x86_reg, map to register_view
|
||||
//
|
||||
else if constexpr ( std::is_same_v<T, x86_reg> )
|
||||
{
|
||||
auto [offset, size] = arch::get_register_mapping( value );
|
||||
auto [base, offset, size] = amd64::resolve_mapping( value );
|
||||
return operand( register_view( value, offset, size ) );
|
||||
}
|
||||
|
||||
// If std::string/register_desc, cast to register_view
|
||||
//
|
||||
else if constexpr ( std::is_same_v<T, std::string> ||
|
||||
std::is_same_v<T, arch::register_desc> )
|
||||
return operand( register_view( value ) );
|
||||
|
||||
// Else, treat as immediate
|
||||
//
|
||||
else if constexpr ( std::is_integral_v<T> )
|
||||
|
|
@ -369,6 +311,11 @@ namespace vtil
|
|||
WRAP_LAZY( band );
|
||||
WRAP_LAZY( bror );
|
||||
WRAP_LAZY( brol );
|
||||
WRAP_LAZY( sets );
|
||||
WRAP_LAZY( setz );
|
||||
WRAP_LAZY( setp );
|
||||
WRAP_LAZY( setc );
|
||||
WRAP_LAZY( seto );
|
||||
WRAP_LAZY( js );
|
||||
WRAP_LAZY( jmp );
|
||||
WRAP_LAZY( vexit );
|
||||
|
|
@ -382,73 +329,23 @@ namespace vtil
|
|||
WRAP_LAZY( vpinwm );
|
||||
#undef WRAP_LAZY
|
||||
|
||||
// Updates EFLAGS register based on the previous instruction executed and writes it
|
||||
// into the operand given.
|
||||
//
|
||||
basic_block* uflags_result( operand op, bool carry );
|
||||
|
||||
// Queues a stack shift.
|
||||
//
|
||||
basic_block* shift_sp( int64_t offset, bool merge_instance = false, iterator it = {} )
|
||||
{
|
||||
// If requested, shift the stack index first.
|
||||
//
|
||||
if ( merge_instance )
|
||||
{
|
||||
// Assert instruction at iterator indeed resets stack pointer.
|
||||
//
|
||||
fassert( !it.is_end() && it->sp_reset );
|
||||
|
||||
// Decrement stack index for each instruction afterwards.
|
||||
//
|
||||
for ( auto i = std::next( it ); !i.is_end(); i++ )
|
||||
i->sp_index--;
|
||||
sp_index--;
|
||||
|
||||
// Remove the reset flag and merge the offsets.
|
||||
//
|
||||
it->sp_reset = false;
|
||||
offset += it->sp_offset;
|
||||
it->sp_offset = 0;
|
||||
}
|
||||
basic_block* shift_sp( int64_t offset, bool merge_instance = false, iterator it = {} );
|
||||
|
||||
// If an iterator is provided, shift the stack pointer
|
||||
// for every instruction that precedes it as well.
|
||||
//
|
||||
std::optional<uint32_t> sp_index_prev;
|
||||
while ( !it.is_end() )
|
||||
{
|
||||
// Shift the stack offset accordingly.
|
||||
//
|
||||
it->sp_offset += offset;
|
||||
// Pushes current flags value up the stack queueing the
|
||||
// shift in stack pointer.
|
||||
//
|
||||
basic_block* pushf();
|
||||
|
||||
// If instruction reads from RSP:
|
||||
//
|
||||
if ( it->reads_from( X86_REG_RSP ) )
|
||||
{
|
||||
// If LDR|STR with memory operand RSP:
|
||||
//
|
||||
if ( it->base->accesses_memory() && it->operands[ it->base->memory_operand_index ].reg == X86_REG_RSP )
|
||||
{
|
||||
// Assert the offset operand is an immediate and
|
||||
// shift the offset as well.
|
||||
//
|
||||
fassert( it->operands[ it->base->memory_operand_index + 1 ].is_immediate() );
|
||||
it->operands[ it->base->memory_operand_index + 1 ].i64 += offset;
|
||||
}
|
||||
}
|
||||
|
||||
// If stack changed changed, return, else forward the iterator.
|
||||
//
|
||||
if ( sp_index_prev.value_or( it->sp_index ) != it->sp_index )
|
||||
return this;
|
||||
sp_index_prev = it->sp_index;
|
||||
++it;
|
||||
}
|
||||
|
||||
// Shift the stack pointer and continue as usual
|
||||
// without emitting any sub or add instructions.
|
||||
// Queued stack pointer changes will be processed
|
||||
// in bulk at the end of the routine.
|
||||
//
|
||||
sp_offset += offset;
|
||||
return this;
|
||||
}
|
||||
// Emits an entire instruction using series of VEMITs.
|
||||
//
|
||||
basic_block* vemits( const std::string& assembly );
|
||||
|
||||
// Pushes an operand up the stack queueing the
|
||||
// shift in stack pointer.
|
||||
|
|
@ -466,7 +363,7 @@ namespace vtil
|
|||
auto t0 = tmp( 8 );
|
||||
return mov( t0, op )->push( t0 );
|
||||
}
|
||||
|
||||
|
||||
shift_sp( op.size() < stack_alignment ? -stack_alignment : -op.size() );
|
||||
str( X86_REG_RSP, sp_offset, op );
|
||||
return this;
|
||||
|
|
@ -484,25 +381,6 @@ namespace vtil
|
|||
ldd( op, X86_REG_RSP, offset );
|
||||
return this;
|
||||
}
|
||||
|
||||
// Pushes current flags value up the stack queueing the
|
||||
// shift in stack pointer.
|
||||
//
|
||||
basic_block* pushf()
|
||||
{
|
||||
return push( X86_REG_EFLAGS );
|
||||
}
|
||||
|
||||
// Emits an entire instruction using series of VEMITs.
|
||||
//
|
||||
basic_block* vemits( const std::string& assembly )
|
||||
{
|
||||
auto res = assemble( assembly );
|
||||
fassert( !res.empty() );
|
||||
for ( uint8_t byte : res )
|
||||
vemit( byte );
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
// Export iterator type for the sake of convinience.
|
||||
|
|
|
|||
159
VTIL-Architecture/routine/instruction.cpp
Normal file
159
VTIL-Architecture/routine/instruction.cpp
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#include "instruction.hpp"
|
||||
|
||||
namespace vtil
|
||||
{
|
||||
// Returns whether the instruction is valid or not.
|
||||
//
|
||||
bool vtil::instruction::is_valid() const
|
||||
{
|
||||
// Instruction must have a base descriptor assigned.
|
||||
//
|
||||
if ( !base )
|
||||
return false;
|
||||
|
||||
// Validate operand count.
|
||||
//
|
||||
if ( operands.size() != base->operand_count() )
|
||||
return false;
|
||||
|
||||
// Validate operand types against the base access type.
|
||||
//
|
||||
for ( int i = 0; i < base->access_types.size(); i++ )
|
||||
{
|
||||
if ( !operands[ i ].is_valid() )
|
||||
return false;
|
||||
if ( base->access_types[ i ] == arch::read_imm && !operands[ i ].is_immediate() )
|
||||
return false;
|
||||
if ( base->access_types[ i ] == arch::read_reg && !operands[ i ].is_register() )
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate memory operands.
|
||||
//
|
||||
if ( base->accesses_memory() )
|
||||
{
|
||||
const operand& mem_base = operands[ base->memory_operand_index ];
|
||||
const operand& mem_offset = operands[ base->memory_operand_index + 1 ];
|
||||
if ( !mem_base.is_register() || mem_base.size() != 8 )
|
||||
return false;
|
||||
if ( !mem_offset.is_immediate() )
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate branching operands.
|
||||
//
|
||||
for ( auto& list : { base->branch_operands_rip, base->branch_operands_vip } )
|
||||
{
|
||||
for ( int idx : list )
|
||||
{
|
||||
if ( operands[ idx ].size() != 8 )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns all memory accesses matching the criteria.
|
||||
//
|
||||
std::pair<arch::register_view, int64_t> instruction::get_mem_loc( arch::operand_access access ) const
|
||||
{
|
||||
// Validate arguments.
|
||||
//
|
||||
fassert( access == arch::invalid || access == arch::read || access == arch::write );
|
||||
|
||||
// If instruction does access memory:
|
||||
//
|
||||
if ( base->accesses_memory() )
|
||||
{
|
||||
// Fetch and validate memory operands pair.
|
||||
//
|
||||
const register_view& mem_base = operands[ base->memory_operand_index ].reg;
|
||||
const operand& mem_offset = operands[ base->memory_operand_index + 1 ];
|
||||
|
||||
if ( !base->memory_write && ( access == arch::read || access == arch::invalid ) )
|
||||
return { mem_base, mem_offset.i64 };
|
||||
else if ( base->memory_write && ( access == arch::write || access == arch::invalid ) )
|
||||
return { mem_base, mem_offset.i64 };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Checks whether the instruction reads from the given register or not.
|
||||
//
|
||||
int instruction::reads_from( const register_view& rw ) const
|
||||
{
|
||||
for ( int i = 0; i < base->access_types.size(); i++ )
|
||||
if ( base->access_types[ i ] != arch::write && operands[ i ].reg.overlaps( rw ) )
|
||||
return i + 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Checks whether the instruction writes to the given register or not.
|
||||
//
|
||||
int instruction::writes_to( const register_view& rw ) const
|
||||
{
|
||||
for ( int i = 0; i < base->access_types.size(); i++ )
|
||||
if ( base->access_types[ i ] >= arch::write && operands[ i ].reg.overlaps( rw ) )
|
||||
return i + 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Checks whether the instruction overwrites the given register or not.
|
||||
//
|
||||
int instruction::overwrites( const register_view& rw ) const
|
||||
{
|
||||
for ( int i = 0; i < base->access_types.size(); i++ )
|
||||
if ( base->access_types[ i ] == arch::write && operands[ i ].reg.overlaps( rw ) )
|
||||
return i + 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Conversion to human-readable format.
|
||||
//
|
||||
std::string instruction::to_string() const
|
||||
{
|
||||
std::vector<std::string> operand_str;
|
||||
for ( auto& op : operands )
|
||||
operand_str.push_back( op.to_string() );
|
||||
fassert( operand_str.size() <= arch::max_operand_count &&
|
||||
arch::max_operand_count == 4 );
|
||||
operand_str.resize( arch::max_operand_count );
|
||||
|
||||
return format::str
|
||||
(
|
||||
FMT_INS,
|
||||
base->to_string( access_size() ),
|
||||
operand_str[ 0 ],
|
||||
operand_str[ 1 ],
|
||||
operand_str[ 2 ],
|
||||
operand_str[ 3 ]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,8 +1,34 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include "..\arch\instruction_set.hpp"
|
||||
#include "..\misc\format.hpp"
|
||||
|
||||
namespace vtil
|
||||
{
|
||||
|
|
@ -15,7 +41,8 @@ namespace vtil
|
|||
|
||||
// Simple helper to create an immediate operand since vtil::operand( v, size ) gets redundant.
|
||||
//
|
||||
template<typename T> static operand make_imm( T value ) { return operand( value, sizeof( T ) ); }
|
||||
template<typename T>
|
||||
static operand make_imm( T value ) { return operand( value, sizeof( T ) ); }
|
||||
|
||||
// Type we use to describe virtual instruction pointer in.
|
||||
//
|
||||
|
|
@ -29,7 +56,7 @@ namespace vtil
|
|||
{
|
||||
// Base instruction type.
|
||||
//
|
||||
const arch::instruction_desc* base;
|
||||
const arch::instruction_desc* base = nullptr;
|
||||
|
||||
// List of operands.
|
||||
//
|
||||
|
|
@ -55,7 +82,7 @@ namespace vtil
|
|||
// Basic constructor, non-default constructor asserts the constructed
|
||||
// instruction is valid according to the instruction descriptor.
|
||||
//
|
||||
instruction() {}
|
||||
instruction() = default;
|
||||
instruction( const arch::instruction_desc* base,
|
||||
const std::vector<operand>& operands = {},
|
||||
vip_t vip = invalid_vip,
|
||||
|
|
@ -68,55 +95,7 @@ namespace vtil
|
|||
|
||||
// Returns whether the instruction is valid or not.
|
||||
//
|
||||
bool is_valid() const
|
||||
{
|
||||
// Instruction must have a base descriptor assigned.
|
||||
//
|
||||
if ( !base )
|
||||
return false;
|
||||
|
||||
// Validate operand count.
|
||||
//
|
||||
if ( operands.size() != base->operand_count() )
|
||||
return false;
|
||||
|
||||
// Validate operand types against the base access type.
|
||||
//
|
||||
for ( int i = 0; i < base->access_types.size(); i++ )
|
||||
{
|
||||
if ( !operands[ i ].is_valid() )
|
||||
return false;
|
||||
if ( base->access_types[ i ] == arch::read_imm && !operands[ i ].is_immediate() )
|
||||
return false;
|
||||
if ( base->access_types[ i ] == arch::read_reg && !operands[ i ].is_register() )
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate memory operands.
|
||||
//
|
||||
if ( base->accesses_memory() )
|
||||
{
|
||||
const operand& mem_base = operands[ base->memory_operand_index ];
|
||||
const operand& mem_offset = operands[ base->memory_operand_index + 1 ];
|
||||
if ( !mem_base.is_register() || mem_base.size() != 8 )
|
||||
return false;
|
||||
if ( !mem_offset.is_immediate() )
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate branching operands.
|
||||
//
|
||||
for ( auto& list : { base->branch_operands_rip, base->branch_operands_vip } )
|
||||
{
|
||||
for ( int idx : list )
|
||||
{
|
||||
if ( operands[ idx ].size() != 8 )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_valid() const;
|
||||
|
||||
// Makes the instruction explicitly volatile.
|
||||
//
|
||||
|
|
@ -137,58 +116,19 @@ namespace vtil
|
|||
|
||||
// Returns all memory accesses matching the criteria.
|
||||
//
|
||||
std::pair<arch::register_view, int64_t> get_mem_loc( arch::operand_access access = arch::invalid ) const
|
||||
{
|
||||
// Validate arguments.
|
||||
//
|
||||
fassert( access == arch::invalid || access == arch::read || access == arch::write );
|
||||
|
||||
// If instruction does access memory:
|
||||
//
|
||||
if ( base->accesses_memory() )
|
||||
{
|
||||
// Fetch and validate memory operands pair.
|
||||
//
|
||||
const register_view& mem_base = operands[ base->memory_operand_index ].reg;
|
||||
const operand& mem_offset = operands[ base->memory_operand_index + 1 ];
|
||||
|
||||
if ( !base->memory_write && ( access == arch::read || access == arch::invalid ) )
|
||||
return { mem_base, mem_offset.i64 };
|
||||
else if ( base->memory_write && ( access == arch::write || access == arch::invalid ) )
|
||||
return { mem_base, mem_offset.i64 };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
std::pair<arch::register_view, int64_t> get_mem_loc( arch::operand_access access = arch::invalid ) const;
|
||||
|
||||
// Checks whether the instruction reads from the given register or not.
|
||||
//
|
||||
bool reads_from( const register_view& rw ) const
|
||||
{
|
||||
for ( int i = 0; i < base->access_types.size(); i++ )
|
||||
if ( base->access_types[ i ] != arch::write && operands[ i ].reg.overlaps( rw ) )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
int reads_from( const register_view& rw ) const;
|
||||
|
||||
// Checks whether the instruction writes to the given register or not.
|
||||
//
|
||||
bool writes_to( const register_view& rw ) const
|
||||
{
|
||||
for ( int i = 0; i < base->access_types.size(); i++ )
|
||||
if ( base->access_types[ i ] >= arch::write && operands[ i ].reg.overlaps( rw ) )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
int writes_to( const register_view& rw ) const;
|
||||
|
||||
// Checks whether the instruction overwrites the given register or not.
|
||||
//
|
||||
bool overwrites( const register_view& rw ) const
|
||||
{
|
||||
for ( int i = 0; i < base->access_types.size(); i++ )
|
||||
if ( base->access_types[ i ] == arch::write && operands[ i ].reg.overlaps( rw ) )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
int overwrites( const register_view& rw ) const;
|
||||
|
||||
// Basic comparison operators.
|
||||
//
|
||||
|
|
@ -198,24 +138,6 @@ namespace vtil
|
|||
|
||||
// Conversion to human-readable format.
|
||||
//
|
||||
std::string to_string() const
|
||||
{
|
||||
std::vector<std::string> operand_str;
|
||||
for ( auto& op : operands )
|
||||
operand_str.push_back( op.to_string() );
|
||||
fassert( operand_str.size() <= arch::max_operand_count &&
|
||||
arch::max_operand_count == 4 );
|
||||
operand_str.resize( arch::max_operand_count );
|
||||
|
||||
return format::str
|
||||
(
|
||||
FMT_INS,
|
||||
base->to_string( access_size() ),
|
||||
operand_str[ 0 ],
|
||||
operand_str[ 1 ],
|
||||
operand_str[ 2 ],
|
||||
operand_str[ 3 ]
|
||||
);
|
||||
}
|
||||
std::string to_string() const;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,30 @@
|
|||
// Copyright (c) 2020 Can Boluk and contributors of the VTIL Project
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// 3. Neither the name of mosquitto nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
#pragma once
|
||||
#include <map>
|
||||
#include <atomic>
|
||||
|
|
|
|||
|
|
@ -1,461 +0,0 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include "variable.hpp"
|
||||
#include "operators.hpp"
|
||||
|
||||
namespace vtil::symbolic
|
||||
{
|
||||
struct expression;
|
||||
using symbol_set = std::set<unique_identifier>;
|
||||
using symbol_map = std::map<variable, expression>;
|
||||
using fn_exp_callback = std::function<void( expression& )>;
|
||||
using fn_const_exp_callback = std::function<void( const expression& )>;
|
||||
|
||||
// Describes an expression tree.
|
||||
//
|
||||
struct expression
|
||||
{
|
||||
// If variable, the value of the expression.
|
||||
//
|
||||
std::optional<variable> value;
|
||||
|
||||
// If result, list of operands and the operator description.
|
||||
//
|
||||
const operator_desc* fn;
|
||||
std::vector<expression> operands;
|
||||
|
||||
// Set to true if the expression was already simplified by the simplifier.
|
||||
// This allows us to indicate that this expression cannot be simplified
|
||||
// any further and thus makes recursive simplification much more time
|
||||
// efficient when it's called from multiple points of the application.
|
||||
//
|
||||
bool is_simplest_form = false;
|
||||
auto& declare_simple() { is_simplest_form = true; return *this; }
|
||||
auto& declare_changed() { is_simplest_form = false; return *this; }
|
||||
|
||||
// Default constructors.
|
||||
//
|
||||
expression() : fn( nullptr ) {}
|
||||
expression( const variable& a ) : value( a ), fn( nullptr ), is_simplest_form( true ) {}
|
||||
expression( const operator_desc* fn, const expression& a ) : operands( { a } ), fn( fn ) {}
|
||||
expression( const expression& a, const operator_desc* fn, const expression& b ) : operands( { a, b } ), fn( fn ) {}
|
||||
|
||||
// Helpers to determine the type of the expression.
|
||||
//
|
||||
bool is_expression() const { return operands.size() && !value; }
|
||||
bool is_variable() const { return operands.empty() && value; }
|
||||
bool is_constant() const { return is_variable() && value->is_constant(); }
|
||||
bool is_valid() const { return is_variable() ? operands.empty() : fn && ( fn->is_unary ? 1 : 2 ) == operands.size(); }
|
||||
|
||||
// Calls the callback provided for every expression that
|
||||
// is actually a boxed symbolic variable.
|
||||
//
|
||||
void enum_symbols( const fn_exp_callback& cb )
|
||||
{
|
||||
if ( is_variable() )
|
||||
{
|
||||
if ( value->is_symbolic() )
|
||||
cb( *this );
|
||||
return;
|
||||
}
|
||||
for ( auto& op : operands )
|
||||
op.enum_symbols( cb );
|
||||
}
|
||||
|
||||
// Calls the callback provided for every expression that
|
||||
// is actually a boxed symbolic variable. [Const]
|
||||
//
|
||||
void enum_symbols( const fn_const_exp_callback& cb ) const
|
||||
{
|
||||
if ( is_variable() )
|
||||
{
|
||||
if ( value->is_symbolic() )
|
||||
cb( *this );
|
||||
return;
|
||||
}
|
||||
for ( auto& op : operands )
|
||||
op.enum_symbols( cb );
|
||||
}
|
||||
|
||||
// Creates a set containing every unique symbol used in
|
||||
// this expression tree.
|
||||
//
|
||||
symbol_set enum_symbols() const
|
||||
{
|
||||
symbol_set tmp;
|
||||
enum_symbols( [ & ] ( auto& x ) { tmp.insert( x.value->uid ); } );
|
||||
return tmp;
|
||||
}
|
||||
|
||||
// Returns whether the expression contains the given
|
||||
// symbolic variable in any size or not.
|
||||
//
|
||||
bool contains_symbol( const unique_identifier& uid ) const
|
||||
{
|
||||
if ( is_variable() )
|
||||
return value->uid == uid;
|
||||
for ( auto& operand : operands )
|
||||
if ( operand.contains_symbol( uid ) )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Counts the number of unique symbols used in this
|
||||
// expression tree.
|
||||
//
|
||||
size_t count_symbols() const
|
||||
{
|
||||
return enum_symbols().size();
|
||||
}
|
||||
|
||||
// Remaps every occurance of the symbol<uid> with the
|
||||
// expression provided.
|
||||
//
|
||||
void remap_symbols( const symbol_map& sym_map )
|
||||
{
|
||||
enum_symbols( [ & ]( expression& v )
|
||||
{
|
||||
for ( auto& sym : sym_map )
|
||||
{
|
||||
if ( sym.first.uid == v.value->uid )
|
||||
v = sym.second;
|
||||
}
|
||||
} );
|
||||
}
|
||||
void remap_symbol( const unique_identifier& uid, const expression& as )
|
||||
{
|
||||
enum_symbols( [ & ] ( expression& v )
|
||||
{
|
||||
if ( v.value->uid == uid )
|
||||
v = as;
|
||||
} );
|
||||
}
|
||||
|
||||
// Checks if the expression is normalized.
|
||||
//
|
||||
bool is_normalized() const
|
||||
{
|
||||
if ( is_variable() || fn->is_unary || fn->result_size == 0 )
|
||||
return true;
|
||||
uint8_t s0 = operands[ 0 ].size();
|
||||
uint8_t s1 = operands[ 1 ].size();
|
||||
return s0 == 0 || s1 == 0 || s0 == s1;
|
||||
}
|
||||
|
||||
// Returns the size of the output value.
|
||||
//
|
||||
uint8_t size( bool real_size = false ) const
|
||||
{
|
||||
// If variable, return size as is.
|
||||
//
|
||||
if ( is_variable() )
|
||||
return value->calc_size( false );
|
||||
|
||||
// Exceptional operators:
|
||||
//
|
||||
if ( fn->function == "__zx" ||
|
||||
fn->function == "__sx" )
|
||||
return real_size ? operands[ 0 ].value->get() : operands[ 1 ].value->get();
|
||||
if ( fn->function == "__bcnt" ||
|
||||
fn->function == "__bcntN" )
|
||||
return 1;
|
||||
|
||||
// If unary operator or result size is first operand,
|
||||
// redirect to the first operand.
|
||||
//
|
||||
if ( fn->is_unary || fn->result_size == 0 )
|
||||
return operands[ 0 ].size( real_size );
|
||||
|
||||
// Process according to the operator definition.
|
||||
//
|
||||
uint8_t s0 = operands[ 0 ].size( real_size );
|
||||
uint8_t s1 = operands[ 1 ].size( real_size );
|
||||
if ( fn->result_size == 1 )
|
||||
return std::max( s0, s1 );
|
||||
else
|
||||
return std::min( s0, s1 );
|
||||
}
|
||||
|
||||
// Changes the size of the output value.
|
||||
//
|
||||
expression& resize( uint8_t size, bool sign_extend = false )
|
||||
{
|
||||
// Can't resize to <any size>
|
||||
//
|
||||
if ( size == 0 )
|
||||
return *this;
|
||||
|
||||
// If variable, resize it:
|
||||
//
|
||||
if ( is_variable() )
|
||||
{
|
||||
if ( value->is_symbolic() )
|
||||
{
|
||||
if ( value->size == size )
|
||||
return *this;
|
||||
|
||||
if ( value->size < size )
|
||||
*this = expression( *this, sign_extend ? find_opr( "__sx" ) : find_opr( "__zx" ), variable( size ) );
|
||||
else
|
||||
value->resize( size, sign_extend );
|
||||
}
|
||||
else
|
||||
{
|
||||
value->resize( size, sign_extend );
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
// If result of an operator:
|
||||
//
|
||||
else if( is_expression() )
|
||||
{
|
||||
// If function is an extender, apply to the argument.
|
||||
//
|
||||
if ( fn->function == "__zx" ||
|
||||
fn->function == "__sx" )
|
||||
{
|
||||
*this = operands[ 0 ];
|
||||
resize( size, sign_extend );
|
||||
}
|
||||
// If unary operator or result size is first operand,
|
||||
// redirect to the first operand.
|
||||
//
|
||||
else if ( fn->is_unary || fn->result_size == 0 )
|
||||
{
|
||||
operands[ 0 ].resize( size, !fn->is_bitwise );
|
||||
}
|
||||
// Otherwise, resize both.
|
||||
//
|
||||
else
|
||||
{
|
||||
// Cannot shrink in a simple way if rotate or shift right
|
||||
//
|
||||
if( fn->function == "rol" || fn->function == "ror" || fn->function == "shr" )
|
||||
fassert( size >= operands[ 0 ].size() );
|
||||
|
||||
operands[ 0 ].resize( size, !fn->is_bitwise );
|
||||
operands[ 1 ].resize( size, !fn->is_bitwise );
|
||||
}
|
||||
|
||||
// Declare that the expression was changed.
|
||||
//
|
||||
declare_changed();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Returns an arbitrary value that represents the "complexity"
|
||||
// of the expression. It's used for the simplification algorithm.
|
||||
//
|
||||
size_t complexity() const
|
||||
{
|
||||
// If it's a variable:
|
||||
//
|
||||
if ( is_variable() )
|
||||
return value->is_constant() ? 0 : 1;
|
||||
|
||||
// Exceptional operators:
|
||||
//
|
||||
if ( fn->function == "__bcntN" ||
|
||||
fn->function == "__zx" ||
|
||||
fn->function == "__sx" )
|
||||
return operands[ 0 ].complexity();
|
||||
if ( fn->function == "__bcnt" ||
|
||||
fn->function == "__bcntN" ||
|
||||
fn->function == "__bmask" )
|
||||
return 0;
|
||||
|
||||
// Ideally we want less operations on symbolic variables,
|
||||
// so create an exponentially increasing cost.
|
||||
//
|
||||
if ( fn->is_unary )
|
||||
return operands[ 0 ].complexity() << 1;
|
||||
else
|
||||
return ( operands[ 0 ].complexity() + operands[ 1 ].complexity() ) << 1;
|
||||
}
|
||||
|
||||
// Depth of the operation tree.
|
||||
//
|
||||
size_t depth() const
|
||||
{
|
||||
// If variable, return 1.
|
||||
//
|
||||
if ( is_variable() )
|
||||
return 0;
|
||||
|
||||
// Exceptional operators:
|
||||
//
|
||||
if ( fn->function == "__bcntN" ||
|
||||
fn->function == "__zx" ||
|
||||
fn->function == "__sx" )
|
||||
return operands[ 0 ].depth();
|
||||
if ( fn->function == "__bcnt" ||
|
||||
fn->function == "__bcntN" ||
|
||||
fn->function == "__bmask" )
|
||||
return 0;
|
||||
|
||||
// For each operand, recurse and sum.
|
||||
//
|
||||
size_t out = 1;
|
||||
for ( auto& subexp : operands )
|
||||
out += subexp.depth();
|
||||
return out;
|
||||
}
|
||||
|
||||
// Tries to evaluate the numeric value of a symbolic expression.
|
||||
//
|
||||
std::optional<variable> evaluate() const
|
||||
{
|
||||
// If expression is a boxed variable, return as is.
|
||||
//
|
||||
if ( is_variable() )
|
||||
return is_constant() ? value : std::nullopt;
|
||||
|
||||
// Handle resizing.
|
||||
//
|
||||
if ( fn->function == "__zx" )
|
||||
{
|
||||
auto res = operands[ 0 ].evaluate();
|
||||
if ( res )
|
||||
res->resize( operands[ 1 ].value->get(), false );
|
||||
return res;
|
||||
}
|
||||
else if ( fn->function == "__sx" )
|
||||
{
|
||||
auto res = operands[ 0 ].evaluate();
|
||||
if ( res )
|
||||
res->resize( operands[ 1 ].value->get(), true );
|
||||
return res;
|
||||
}
|
||||
|
||||
auto operands_n = operands;
|
||||
size_t ns = size();
|
||||
|
||||
// ------- Unary operators ------- //
|
||||
if ( fn->function == "__bcnt" )
|
||||
return variable{ operands_n[ 0 ].size() * 8, ns };
|
||||
else if ( fn->function == "__bmask" )
|
||||
return variable{ ~0ull >> ( 64 - operands_n[ 0 ].size() * 8 ), ns };
|
||||
|
||||
variable o1;
|
||||
if ( auto r = operands_n[ 0 ].evaluate() ) o1 = r.value();
|
||||
else return {};
|
||||
|
||||
if ( fn->function == "neg" )
|
||||
return variable{ -o1.get<true>(), ns };
|
||||
else if ( fn->function == "not" )
|
||||
return variable{ ~o1.get(), ns };
|
||||
|
||||
// ------- Binary operators ------- //
|
||||
if ( fn->function == "__bcntN" )
|
||||
return variable{ uint8_t( ( o1.get() ) % ( operands_n[ 1 ].size() * 8 ) ), operands_n[ 0 ].size() };
|
||||
|
||||
variable o2;
|
||||
if ( auto r = operands_n[ 1 ].evaluate() ) o2 = r.value();
|
||||
else return {};
|
||||
|
||||
if ( fn->function == "or" )
|
||||
return variable{ o1.get() | o2.get(), ns };
|
||||
else if ( fn->function == "and" )
|
||||
return variable{ o1.get() & o2.get(), ns };
|
||||
else if ( fn->function == "xor" )
|
||||
return variable{ o1.get() ^ o2.get(), ns };
|
||||
else if ( fn->function == "shr" )
|
||||
return ( ns && o2.get() >= ( ns * 8 ) ) ? variable{ 0, ns } : variable{ o1.get( ns ) >> o2.get(), ns };
|
||||
else if ( fn->function == "shl" )
|
||||
return ( ns && o2.get() >= ( ns * 8 ) ) ? variable{ 0, ns } : variable{ o1.get( ns ) << o2.get(), ns };
|
||||
else if ( fn->function == "ror" )
|
||||
return variable{ ( o1.get( ns ) >> o2.get() ) | ( o1.get( ns ) << ( o1.size * 8 - o2.get() ) ), ns };
|
||||
else if ( fn->function == "rol" )
|
||||
return variable{ ( o1.get( ns ) << o2.get() ) | ( o1.get( ns ) >> ( o1.size * 8 - o2.get() ) ), ns };
|
||||
else if ( fn->function == "add" )
|
||||
return variable{ o1.get<true>() + o2.get<true>(), ns };
|
||||
else if ( fn->function == "sub" )
|
||||
return variable{ o1.get<true>() - o2.get<true>(), ns };
|
||||
|
||||
// Other operators should not reach here.
|
||||
return {};
|
||||
}
|
||||
|
||||
// Conversion to human readable format.
|
||||
//
|
||||
std::string to_string() const
|
||||
{
|
||||
// If variable redirect to it's own ::to_string.
|
||||
//
|
||||
if ( is_variable() )
|
||||
return value->to_string();
|
||||
fassert( fn );
|
||||
|
||||
// If unary function:
|
||||
//
|
||||
if ( fn->is_unary )
|
||||
{
|
||||
fassert( operands.size() == 1 );
|
||||
return fn->symbol.size()
|
||||
? fn->symbol + operands[ 0 ].to_string()
|
||||
: fn->function + "(" + operands[ 0 ].to_string() + ")";
|
||||
}
|
||||
// If binary function:
|
||||
//
|
||||
fassert( operands.size() == 2 );
|
||||
return fn->symbol.size()
|
||||
? "(" + operands[ 0 ].to_string() + fn->symbol + operands[ 1 ].to_string() + ")"
|
||||
: fn->function + "(" + operands[ 0 ].to_string() + ", " + operands[ 1 ].to_string() + ")";
|
||||
}
|
||||
|
||||
// Basic comparison operators.
|
||||
//
|
||||
bool operator==( const expression& o ) const
|
||||
{
|
||||
if ( is_expression() && ( fn->function == "__zx" || fn->function == "__sx" ) )
|
||||
return o == operands[ 0 ];
|
||||
else if ( o.is_variable() )
|
||||
return is_variable() && value == o.value;
|
||||
else
|
||||
return is_expression() && fn == o.fn && operands == o.operands;
|
||||
}
|
||||
bool operator!=( const expression& o ) const { return !operator==( o ); }
|
||||
bool operator<( const expression& o ) const { return !operator==( o ) && to_string() < o.to_string(); }
|
||||
|
||||
// Convinience wrapper for operand access.
|
||||
// - Use the .operands container manually when changing
|
||||
// any operands and clear the simplest form hint.
|
||||
//
|
||||
const expression& operator[]( size_t i ) const { return operands[ i ]; }
|
||||
|
||||
// Convinience wrappers around common operations.
|
||||
//
|
||||
expression operator+() const { return expression( *this ); }
|
||||
expression operator~() const { return expression( find_opr( "not" ), *this ); }
|
||||
expression operator-() const { return expression( find_opr( "neg" ), *this ); }
|
||||
template<typename T> expression ror( T y ) const { return expression( *this, find_opr( "ror" ), expression{ y } ); }
|
||||
template<typename T> expression rol( T y ) const { return expression( *this, find_opr( "rol" ), expression{ y } ); }
|
||||
};
|
||||
|
||||
template<typename T> static expression operator+( const expression& x, T y ) { return { x, find_opr( "add" ), expression{ y } }; }
|
||||
template<typename T> static expression operator-( const expression& x, T y ) { return { x, find_opr( "sub" ), expression{ y } }; }
|
||||
template<typename T> static expression operator|( const expression& x, T y ) { return { x, find_opr( "or" ), expression{ y } }; }
|
||||
template<typename T> static expression operator&( const expression& x, T y ) { return { x, find_opr( "and" ), expression{ y } }; }
|
||||
template<typename T> static expression operator^( const expression& x, T y ) { return { x, find_opr( "xor" ), expression{ y } }; }
|
||||
template<typename T> static expression operator>>( const expression& x, T y ) { return { x, find_opr( "shr" ), expression{ y } }; }
|
||||
template<typename T> static expression operator<<( const expression& x, T y ) { return { x, find_opr( "shl" ), expression{ y } }; }
|
||||
|
||||
template<typename T, std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, expression>, int> = 0>
|
||||
static expression operator+( T x, const expression& y ) { return expression{ x } + y; }
|
||||
template<typename T, std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, expression>, int> = 0>
|
||||
static expression operator-( T x, const expression& y ) { return expression{ x } - y; }
|
||||
template<typename T, std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, expression>, int> = 0>
|
||||
static expression operator|( T x, const expression& y ) { return expression{ x } | y; }
|
||||
template<typename T, std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, expression>, int> = 0>
|
||||
static expression operator&( T x, const expression& y ) { return expression{ x } & y; }
|
||||
template<typename T, std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, expression>, int> = 0>
|
||||
static expression operator^( T x, const expression& y ) { return expression{ x } ^ y; }
|
||||
template<typename T, std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, expression>, int> = 0>
|
||||
static expression operator>>( T x, const expression& y ) { return expression{ x } >> y; }
|
||||
template<typename T, std::enable_if_t<!std::is_same_v<std::remove_cvref_t<T>, expression>, int> = 0>
|
||||
static expression operator<<( T x, const expression& y ) { return expression{ x } << y; }
|
||||
};
|
||||
|
|
@ -1,550 +0,0 @@
|
|||
#pragma once
|
||||
#define SYMEX_EVALTIME_SIMPLIFY 1
|
||||
#include <optional>
|
||||
#include <iterator>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include "variable.hpp"
|
||||
#include "expression.hpp"
|
||||
#include "simplifier.hpp"
|
||||
#include "..\query\view.hpp"
|
||||
#include "..\routine\instruction.hpp"
|
||||
#include "..\routine\basic_block.hpp"
|
||||
|
||||
// Symbolic expression generator is essentialy the core of almost every optimization
|
||||
// pass since it is used to create simplified equivalents for the given
|
||||
// variable, which can be easily created from and cast back to any register
|
||||
// descriptor, view, or a memory pointer including external memory.
|
||||
//
|
||||
namespace vtil::symbolic
|
||||
{
|
||||
// Generates a simplified expression for the given interator-bound variable
|
||||
// according to the instruction stream in the basic block, and any block that
|
||||
// jumps to it if recurse flag is set.
|
||||
//
|
||||
// - Virtual-SP should be set if the caller wishes to receive
|
||||
// virtual stack pointers such as the operands of STR and
|
||||
// LDR where the memory offset behaves indepdentent to the
|
||||
// the stack pointer itself.
|
||||
//
|
||||
// - Handles branching and loops internally.
|
||||
//
|
||||
template<bool verbose = false>
|
||||
static expression generate( const variable& lookup,
|
||||
bool virtual_sp = false,
|
||||
bool recurse = false,
|
||||
int32_t max_op_depth = INT32_MAX,
|
||||
std::map<std::pair<const basic_block*, const basic_block*>, uint32_t> visited = {} )
|
||||
{
|
||||
// If we're not tracing a register or a memory value, return as is.
|
||||
//
|
||||
if ( !lookup.is_register() && !lookup.is_memory() )
|
||||
return lookup;
|
||||
|
||||
// If we are tracing a control register return the symbol as is.
|
||||
//
|
||||
if ( lookup.is_register() )
|
||||
{
|
||||
register_view rw = lookup.get_reg();
|
||||
if ( rw.base.maps_to >= X86_REG_VCR0 )
|
||||
return lookup;
|
||||
}
|
||||
|
||||
// Fail if max operation depth was reached.
|
||||
//
|
||||
if ( max_op_depth < 0 )
|
||||
return {};
|
||||
|
||||
// Resolve query offset, size and iterator.
|
||||
//
|
||||
int32_t query_offset = lookup.is_register() ? lookup.get_reg().offset : 0;
|
||||
uint8_t query_size = lookup.size;
|
||||
fassert( query_size != 0 );
|
||||
|
||||
// Log the beginning of the trace if verbose.
|
||||
//
|
||||
if constexpr ( verbose )
|
||||
{
|
||||
io::log( "=> Tracing %s", lookup.to_string() );
|
||||
io::log_padding++; io::log( "\n" );
|
||||
}
|
||||
|
||||
// Craete the base query.
|
||||
//
|
||||
auto query_base = query::create( lookup.uid.origin, -1 ).unproject();
|
||||
expression pointer_exp = {};
|
||||
uint8_t current_size;
|
||||
int64_t current_offset;
|
||||
|
||||
// If looking up register value:
|
||||
//
|
||||
if ( lookup.is_register() )
|
||||
{
|
||||
// | Filter to instructions that write to the register we're trying to resolve.
|
||||
query_base
|
||||
.where( [ & ] ( const ilstream_const_iterator& it ) { return it->writes_to( lookup.get_reg() ); } );
|
||||
}
|
||||
else
|
||||
{
|
||||
query_base
|
||||
// | Filter to instructions that write to memory.
|
||||
.where( [ ] ( const ilstream_const_iterator& it ) { return it->base->writes_memory(); } )
|
||||
|
||||
// | Filter to instructions that write over our pointer.
|
||||
.where( [ & ] ( const ilstream_const_iterator& it )
|
||||
{
|
||||
auto [mem_base, mem_off] = it->get_mem_loc();
|
||||
|
||||
// Check if both are reading from the same stack instance to optimize
|
||||
// the amount of time this lookup takes by avoiding another recursive call.
|
||||
//
|
||||
if ( mem_base == X86_REG_RSP &&
|
||||
lookup.uid.get_mem().first == X86_REG_RSP &&
|
||||
lookup.uid.memory_base_idx == it->sp_index )
|
||||
{
|
||||
// Offset is equivalent to the delta of their offset operands.
|
||||
//
|
||||
current_offset = mem_off - lookup.uid.get_mem().second;
|
||||
|
||||
// Log write resolved if verbose.
|
||||
//
|
||||
if constexpr ( verbose )
|
||||
io::log<CON_RED>( "Write resolved to [@+%s]\n", format::hex( current_offset ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
// If pointer expression generation was deferred, generate it now.
|
||||
//
|
||||
if ( !pointer_exp.is_valid() )
|
||||
{
|
||||
pointer_exp = generate( { { lookup.uid.get_mem().first, lookup.uid.origin }, 8 }, true ) + lookup.uid.get_mem().second;
|
||||
|
||||
// Log pointer resolved if verbose.
|
||||
//
|
||||
if constexpr ( verbose )
|
||||
io::log<CON_GRN>( "Pointer resolved to [=%s]\n", pointer_exp.to_string() );
|
||||
}
|
||||
|
||||
// Try to simplify the expression for [dst-lookup].
|
||||
//
|
||||
auto offset_exp = simplify( generate( { { it, it->base->memory_operand_index }, 8 }, true ) + mem_off - pointer_exp );
|
||||
|
||||
// Log write resolved if verbose.
|
||||
//
|
||||
if constexpr ( verbose )
|
||||
io::log<CON_RED>( "Write resolved to [@+%s]\n", offset_exp.to_string() );
|
||||
|
||||
// If it does not evaluate to a constant value, skip.
|
||||
//
|
||||
auto offset = offset_exp.evaluate();
|
||||
if ( !offset.has_value() || !offset->is_constant() )
|
||||
return false;
|
||||
|
||||
// Offset is equivalent to the evaluated constant.
|
||||
//
|
||||
current_offset = offset->get<true>();
|
||||
}
|
||||
|
||||
// Apply simple boundary check.
|
||||
//
|
||||
current_size = it->access_size();
|
||||
return -current_size < current_offset && current_offset < lookup.size;
|
||||
} );
|
||||
}
|
||||
|
||||
// Define our query's main logic.
|
||||
//
|
||||
const auto to_result = [ & ] ( ilstream_const_iterator it ) -> expression
|
||||
{
|
||||
auto gen = [ & ] ( const variable& var, bool is_op = false ) { return generate<verbose>( var, false, recurse, max_op_depth - is_op, visited ); };
|
||||
|
||||
// If we are looking up a stack value:
|
||||
//
|
||||
if ( lookup.is_memory() )
|
||||
{
|
||||
// If an unknown/non-representable operation is being executed on
|
||||
// the operand we're tracing, fail the trace.
|
||||
//
|
||||
if ( it->base != &ins::str )
|
||||
return variable( lookup ).bind( it );
|
||||
|
||||
// If size and offset match:
|
||||
//
|
||||
if ( query_offset == current_offset &&
|
||||
query_size == current_size )
|
||||
{
|
||||
// Generate symbolic variable for the result.
|
||||
//
|
||||
return gen( { it, 2 } );
|
||||
}
|
||||
}
|
||||
// Else, if we are looking up a register value:
|
||||
//
|
||||
else
|
||||
{
|
||||
// If an unknown/non-representable operation is being executed on
|
||||
// the operand we're tracing, fail the trace.
|
||||
//
|
||||
if ( it->base != &ins::mov && it->base != &ins::ldd && it->base->symbolic_operator.empty() )
|
||||
return variable( lookup ).bind( it );
|
||||
|
||||
// Generic check for whether the symbol offset/size matches or not.
|
||||
// Will work as every instruction above writes to operand 1.
|
||||
//
|
||||
current_size = it->operands[ 0 ].reg.size;
|
||||
current_offset = it->operands[ 0 ].reg.offset;
|
||||
fassert( it->operands[ 0 ].is_register() );
|
||||
if ( query_offset == current_offset &&
|
||||
query_size == current_size )
|
||||
{
|
||||
// If result is an immediate or simply another register:
|
||||
//
|
||||
if ( it->base == &ins::mov )
|
||||
{
|
||||
// Generate symbolic variable for the result.
|
||||
//
|
||||
return gen( { it, 1 } );
|
||||
}
|
||||
// If result is simply being loaded from external memory:
|
||||
//
|
||||
else if ( it->base == &ins::ldd )
|
||||
{
|
||||
// Generate symbolic variable for the result.
|
||||
//
|
||||
return gen( { it, -1 } );
|
||||
}
|
||||
// If it's the result of a symbolic operator:
|
||||
//
|
||||
else if ( !it->base->symbolic_operator.empty() )
|
||||
{
|
||||
const operator_desc* opr = find_opr( it->base->symbolic_operator );
|
||||
|
||||
// OP1 = F(OP1)
|
||||
//
|
||||
if ( it->operands.size() == 1 )
|
||||
{
|
||||
fassert( opr->is_unary );
|
||||
|
||||
// Resolve the value of OP1 prior to this instruction,
|
||||
// fail if recursive call fails.
|
||||
//
|
||||
expression op1e = gen( { it, 0 }, true );
|
||||
if ( !op1e.is_valid() )
|
||||
return variable( lookup ).bind( it );
|
||||
|
||||
// Describe the operation.
|
||||
//
|
||||
#if SYMEX_EVALTIME_SIMPLIFY
|
||||
return simplify( expression( opr, op1e ) );
|
||||
#else
|
||||
return expression( opr, op1e );
|
||||
#endif
|
||||
}
|
||||
// OP1 = F(OP1, OP2)
|
||||
//
|
||||
else if ( it->operands.size() == 2 )
|
||||
{
|
||||
fassert( !opr->is_unary );
|
||||
|
||||
// Resolve the value of OP1 and OP2 prior to this
|
||||
// instruction, fail if recursive call fails.
|
||||
//
|
||||
expression op1e = gen( { it, 0 }, true );
|
||||
if ( !op1e.is_valid() )
|
||||
return variable( lookup ).bind( it );
|
||||
expression op2e = gen( { it, 1 }, true );
|
||||
if ( !op2e.is_valid() )
|
||||
return variable( lookup ).bind( it );
|
||||
|
||||
// Describe the operation.
|
||||
//
|
||||
#if SYMEX_EVALTIME_SIMPLIFY
|
||||
return simplify( expression( op1e, opr, op2e ) );
|
||||
#else
|
||||
return expression( op1e, opr, op2e );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// >>> Begin size / offset mismatch logic <<<
|
||||
/////////////////////////////////////////////
|
||||
|
||||
// Helper to calculate the exact symbol we would have resolved.
|
||||
//
|
||||
const auto query_sub = [ & ] ( int64_t offset, uint8_t size )
|
||||
{
|
||||
// Create the symbolic identifier for the segment
|
||||
// and try to generate an expresion.
|
||||
//
|
||||
variable sub_lookup = lookup;
|
||||
sub_lookup.size = size;
|
||||
if ( lookup.is_register() )
|
||||
sub_lookup.uid.register_id->offset = offset, sub_lookup.uid.register_id->size = size;
|
||||
else if ( lookup.is_memory() )
|
||||
sub_lookup.uid.memory_id = { sub_lookup.uid.memory_id->first, sub_lookup.uid.memory_id->second + offset };
|
||||
else
|
||||
unreachable();
|
||||
sub_lookup.uid.origin = std::next( sub_lookup.uid.origin );
|
||||
sub_lookup.uid.refresh();
|
||||
expression exp = gen( sub_lookup );
|
||||
|
||||
// If we failed, return as is.
|
||||
//
|
||||
if ( !exp.is_valid() )
|
||||
return exp;
|
||||
exp.resize( query_size );
|
||||
|
||||
// Mask the result (and resize, abusing size == max(x.size, y.size))
|
||||
//
|
||||
exp = exp & variable( ~0ull >> ( 64 - size * 8 ), query_size );
|
||||
|
||||
// If result belongs to low segment, shift left:
|
||||
//
|
||||
if ( offset > query_offset )
|
||||
exp = exp << variable( ( offset - query_offset ) * 8, query_size );
|
||||
|
||||
// If result belongs to high segment, shift right:
|
||||
//
|
||||
else if ( offset < query_offset )
|
||||
exp = exp >> variable( ( query_offset - offset ) * 8, query_size );
|
||||
|
||||
// Return the new expression.
|
||||
//
|
||||
return exp;
|
||||
};
|
||||
|
||||
// Query current data we have with no mistmatch, fail if query fails.
|
||||
//
|
||||
expression result = query_sub( current_offset, current_size );
|
||||
if ( !result.is_valid() )
|
||||
return result;
|
||||
|
||||
// If we have enough information to return as is do not query any other segments
|
||||
//
|
||||
if ( ( query_size + query_offset ) <= ( current_offset + current_size ) && query_offset >= current_offset )
|
||||
{
|
||||
// <No operation>
|
||||
}
|
||||
// If the offsets match, we need to merge a high segment to the current result.
|
||||
//
|
||||
else if ( current_offset == query_offset )
|
||||
{
|
||||
fassert( current_size < query_size );
|
||||
|
||||
// Query high and merge it to the result.
|
||||
//
|
||||
expression high_res = query_sub( current_offset + current_size, query_size - current_size );
|
||||
if ( !high_res.is_valid() )
|
||||
return high_res;
|
||||
result = result | high_res;
|
||||
}
|
||||
// If offsets do not match, we need to 2 <= x <= 3 segments
|
||||
//
|
||||
else
|
||||
{
|
||||
// Query low (if relevant) and merge it to the result.
|
||||
//
|
||||
if ( query_offset < current_offset )
|
||||
{
|
||||
expression low_res = query_sub( query_offset, current_offset - query_offset );
|
||||
if ( !low_res.is_valid() )
|
||||
return low_res;
|
||||
result = result | low_res;
|
||||
}
|
||||
|
||||
// Query high (if relevant) and merge it to the result.
|
||||
//
|
||||
int32_t off_query_end = query_offset + query_size;
|
||||
int32_t off_current_end = current_offset + current_size;
|
||||
if ( off_query_end > off_current_end )
|
||||
{
|
||||
expression high_res = query_sub( off_current_end, off_query_end - off_current_end );
|
||||
if ( !high_res.is_valid() )
|
||||
return high_res;
|
||||
result = result | high_res;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the result.
|
||||
//
|
||||
#if SYMEX_EVALTIME_SIMPLIFY
|
||||
return simplify( result );
|
||||
#else
|
||||
return result;
|
||||
#endif
|
||||
};
|
||||
|
||||
// Declare the output expression and the default expression.
|
||||
//
|
||||
expression result = {};
|
||||
expression default_result = variable( lookup ).unbind();
|
||||
|
||||
// If looking up stack pointer and flag to adjust is set:
|
||||
//
|
||||
int64_t adjustment_offset = 0;
|
||||
if ( lookup.is_register() && lookup.get_reg() == X86_REG_RSP && !virtual_sp )
|
||||
adjustment_offset = lookup.uid.origin.is_end() ? lookup.uid.origin.container->sp_offset : lookup.uid.origin->sp_offset;
|
||||
|
||||
// If we could find a local result within the current block assign it as is.
|
||||
//
|
||||
if ( std::optional result_p = query_base.reproject( to_result ).first() )
|
||||
{
|
||||
result = result_p.value();
|
||||
}
|
||||
// If recursive scanning is allowed, try recursing into previous blocks.
|
||||
//
|
||||
else if ( recurse )
|
||||
{
|
||||
// Generate the list of iterators we could continue from.
|
||||
//
|
||||
std::vector it_list = query_base.query.iterator.recurse( false );
|
||||
|
||||
// If only valid previous block is self, one self-referencing result is valid.
|
||||
//
|
||||
bool expect_self_ref = false;
|
||||
if ( it_list.size() == 1 && it_list[ 0 ].container == lookup.uid.origin.container )
|
||||
{
|
||||
expect_self_ref = lookup.uid.origin.is_begin();
|
||||
it_list.push_back( { lookup.uid.origin.container->begin() } );
|
||||
}
|
||||
|
||||
// For each possible route:
|
||||
//
|
||||
for ( auto it : it_list )
|
||||
{
|
||||
// Create a local copy for the visited list for this path
|
||||
// and increment the visit counter.
|
||||
//
|
||||
std::map visited_local = visited;
|
||||
uint32_t& visit_counter = visited_local[ { lookup.uid.origin.container, it.container } ];
|
||||
int32_t blk_sp_offset = it.is_begin() ? 0 : it.container->sp_offset;
|
||||
|
||||
// If we've taken this route no more than once:
|
||||
//
|
||||
if ( visit_counter++ <= 1 )
|
||||
{
|
||||
// Transform the variable over to the destination block.
|
||||
//
|
||||
variable var_inherited = variable( lookup ).bind( it );
|
||||
if ( lookup.is_memory() )
|
||||
{
|
||||
// If pointer expression generation was deferred, generate it now.
|
||||
//
|
||||
if ( !pointer_exp.is_valid() )
|
||||
pointer_exp = generate( { { lookup.uid.get_mem().first, lookup.uid.origin }, 8 }, true ) + lookup.uid.get_mem().second;
|
||||
|
||||
// Log pointer resolved if verbose.
|
||||
//
|
||||
if constexpr ( verbose )
|
||||
io::log<CON_CYN>( "Pointer adjusted to [=%s]\n", pointer_exp.to_string() );
|
||||
|
||||
// If the pointer can be rewritten in the form of [RSP + C]:
|
||||
//
|
||||
if ( auto off = simplify( pointer_exp - variable{ { register_view{ X86_REG_RSP } }, 8 } ).evaluate() )
|
||||
{
|
||||
// Assign the new adjusted offset.
|
||||
//
|
||||
var_inherited.uid.assign( X86_REG_RSP, blk_sp_offset - off->get<true>() );
|
||||
}
|
||||
// If the delta does not simplify to a constant stop recursing and fail.
|
||||
//
|
||||
else
|
||||
{
|
||||
result = {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a expression for the variable in the destination block.
|
||||
// - Read note below to understand why virtual sp == true.
|
||||
//
|
||||
expression exp = generate<verbose>( var_inherited, true, recurse, max_op_depth, visited_local );
|
||||
|
||||
// Skip if we traced back to the lookup variable
|
||||
//
|
||||
if( !expect_self_ref && is_equivalent( exp, default_result + adjustment_offset ) )
|
||||
{
|
||||
// Log decision if verbose.
|
||||
//
|
||||
if constexpr ( verbose )
|
||||
io::log<CON_YLW>( "Candidate [%s] was rejected as it's self-referencing.\n", exp.to_string() );
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we are tracing the value of RSP, add the stack pointer delta between blocks since
|
||||
// that will be the adjustment offset of the next block and we will request a virtual
|
||||
// stack pointer to keep things in balance.
|
||||
//
|
||||
if ( lookup.is_register() && lookup.get_reg() == X86_REG_RSP )
|
||||
exp = exp + blk_sp_offset;
|
||||
|
||||
// If no result is set yet, assign the current expression:
|
||||
//
|
||||
if ( !result.is_valid() )
|
||||
{
|
||||
// Log decision if verbose.
|
||||
//
|
||||
if constexpr ( verbose )
|
||||
io::log<CON_GRN>( "Using [%s] as primary candidate.\n", exp.to_string() );
|
||||
result = exp;
|
||||
}
|
||||
// If previously set result is not equivalent to current expression
|
||||
// hint branch dependency and return default result:
|
||||
//
|
||||
else if( !is_equivalent( exp, result ) )
|
||||
{
|
||||
// Log decision if verbose.
|
||||
//
|
||||
if constexpr ( verbose )
|
||||
io::log<CON_RED>( "Cancelling query as candidate [%s] differs from previously set candidate.\n", exp.to_string() );
|
||||
result = {};
|
||||
|
||||
default_result.enum_symbols( [ & ] ( expression& v )
|
||||
{
|
||||
v.value->uid.set_branch_dependency( true );
|
||||
v.value->uid.bind( lookup.uid.origin.container->begin() );
|
||||
} );
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Log decision if verbose.
|
||||
//
|
||||
if constexpr ( verbose )
|
||||
io::log<CON_YLW>( "Continuing query since [%s] matched primary candidate.\n", exp.to_string() );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Log skipping of path if verbose.
|
||||
//
|
||||
if constexpr ( verbose )
|
||||
io::log<CON_CYN>( "Path [%llx->%llx] is not taken as it's n-looping.\n", lookup.uid.origin.container->entry_vip, it.container->entry_vip );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If resolved result is not valid, assign the default result.
|
||||
//
|
||||
if ( !result.is_valid() )
|
||||
result = default_result;
|
||||
|
||||
// Propagate the stack adjustment.
|
||||
//
|
||||
if ( adjustment_offset != 0 )
|
||||
result = simplify( result + variable( adjustment_offset, 8 ) );
|
||||
|
||||
// Log the final result if verbose.
|
||||
//
|
||||
if constexpr ( verbose )
|
||||
{
|
||||
io::log_padding--;
|
||||
io::log( "= %s\n", result.to_string() );
|
||||
}
|
||||
return result.resize( lookup.size );
|
||||
}
|
||||
};
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
#pragma once
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <platform.hpp>
|
||||
|
||||
namespace vtil::symbolic
|
||||
{
|
||||
// Generic operator description.
|
||||
//
|
||||
struct operator_desc
|
||||
{
|
||||
// Symbolic identifier for this operator.
|
||||
//
|
||||
// If [symbol] is empty:
|
||||
// to_string(...) => function(split(..., ','))
|
||||
// else:
|
||||
// to_string(x) => concat(symbol, x)
|
||||
// to_string(...) => split(..., symbol)
|
||||
//
|
||||
std::string function;
|
||||
std::string symbol;
|
||||
|
||||
// Whether this is a unary operator or not.
|
||||
//
|
||||
bool is_unary;
|
||||
|
||||
// Whether this is a bitwise operator or not.
|
||||
//
|
||||
bool is_bitwise;
|
||||
|
||||
// Whether (anti-)commutative property is applicable or not.
|
||||
// N/A if 0, anti-commutative if -1, commutative if +1.
|
||||
int8_t commutative;
|
||||
|
||||
// Type of result size, min if -1, max if +1, first operand if 0.
|
||||
//
|
||||
int8_t result_size;
|
||||
|
||||
// Null and identity operands in terms of x.
|
||||
//
|
||||
std::vector<std::string> null_operand;
|
||||
std::vector<std::string> identity_operand;
|
||||
};
|
||||
|
||||
// List of all operators.
|
||||
//
|
||||
static const std::vector<operator_desc> operator_map =
|
||||
{
|
||||
// [Name] [Symbol] [Unary?] [Bitwise?] [Commutative] [RSize] //
|
||||
{ "neg", "-", true, false, 0, 0, },
|
||||
{ "not", "~", true, true, 0, 0, },
|
||||
|
||||
{ "add", "+", false, false, +1, +1, },
|
||||
{ "sub", "-", false, false, -1, +1, },
|
||||
|
||||
{ "or" , "|", false, true, +1, +1, },
|
||||
{ "and", "&", false, true, +1, -1, },
|
||||
{ "xor", "^", false, true, +1, +1, },
|
||||
{ "shr", ">>", false, true, 0, 0, },
|
||||
{ "shl", "<<", false, true, 0, 0, },
|
||||
{ "ror", ">]", false, true, 0, 0, },
|
||||
{ "rol", "[<", false, true, 0, 0, },
|
||||
|
||||
// Special operands for simplification instructions:
|
||||
//
|
||||
|
||||
// Bit-Count Normalize:
|
||||
// - Evaluates to op#1 % bcnt(op#2) [Note: Will only match if op#1 >= bcnt(op#2) || op#1 < 0]
|
||||
//
|
||||
{ "__bcntN", "", false, true, 0, 0, },
|
||||
|
||||
// Bit-Mask:
|
||||
// - Evaluates to ~0{ of size op#1}
|
||||
//
|
||||
{ "__bmask", "", true, true, 0, 0, },
|
||||
|
||||
// Bit-Mask:
|
||||
// - Evaluates to size op#1
|
||||
//
|
||||
{ "__bcnt", "", true, true, 0, 0, },
|
||||
|
||||
// Extension:
|
||||
// - Hints that op#1 was extended.
|
||||
//
|
||||
{ "__sx", "", false, true, 0, 0, },
|
||||
{ "__zx", "", false, true, 0, 0, },
|
||||
};
|
||||
|
||||
// Searcher for an operator within the string provided.
|
||||
//
|
||||
static const operator_desc* lookup( const std::string& s, bool partial, bool prefix )
|
||||
{
|
||||
// For each operator in the list:
|
||||
//
|
||||
for ( auto& desc : operator_map )
|
||||
{
|
||||
// Match symbols only if we're looking for a suffix or
|
||||
// this is a unary operator.
|
||||
//
|
||||
if ( !prefix || desc.is_unary )
|
||||
{
|
||||
if ( !desc.symbol.empty() && ( partial ? s.starts_with( desc.symbol ) : s == desc.symbol ) )
|
||||
return &desc;
|
||||
}
|
||||
|
||||
// Match function names only if we're looking for a prefix.
|
||||
//
|
||||
if ( prefix && ( partial ? s.starts_with( desc.function ) : s == desc.function ) )
|
||||
{
|
||||
return &desc;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Convinience wrapper for (what would be, if the map was not const) operator_map[name].
|
||||
//
|
||||
static const operator_desc* find_opr( const std::string& name )
|
||||
{
|
||||
// For each operator in the list:
|
||||
//
|
||||
for ( auto& desc : operator_map )
|
||||
{
|
||||
if ( desc.function == name )
|
||||
return &desc;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
};
|
||||
|
|
@ -1,495 +0,0 @@
|
|||
#pragma once
|
||||
#include <tuple>
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
#include <numeric>
|
||||
#include "expression.hpp"
|
||||
#include "variable.hpp"
|
||||
|
||||
namespace vtil::symbolic::rules
|
||||
{
|
||||
// Describes an entry in the rule tables.
|
||||
//
|
||||
struct rule_entry
|
||||
{
|
||||
// Number of times this rule entry was used to produce optimal result.
|
||||
//
|
||||
volatile uint32_t points = 0;
|
||||
|
||||
// Base expression containing symbolic expression tree to match against.
|
||||
//
|
||||
const expression base_expression;
|
||||
|
||||
// Function that performs any additional checks/processing after initial matching.
|
||||
//
|
||||
std::function<bool( symbol_map& )> extension = {};
|
||||
|
||||
// Constructors for simple and complex logic.
|
||||
//
|
||||
rule_entry( expression exp ) : base_expression( exp ) {}
|
||||
template<typename T>
|
||||
rule_entry( expression exp, T extension ) : base_expression( exp ), extension( extension ) {}
|
||||
|
||||
// Basic hash function implementation so we can place it in an unordered map.
|
||||
//
|
||||
struct hash { size_t operator()( const rule_entry& self ) const { return std::hash<std::string>()( self.base_expression.to_string() ); } };
|
||||
bool operator==( const rule_entry& o ) const { return base_expression == o.base_expression; }
|
||||
};
|
||||
|
||||
// Symbolic variables used in rule creation:
|
||||
//
|
||||
static const expression A = { { "α", 0 } };
|
||||
static const expression B = { { "β", 0 } };
|
||||
static const expression C = { { "λ", 0 } };
|
||||
|
||||
// Special variables:
|
||||
//
|
||||
static const expression X = { { "Σ", 0 } };
|
||||
static const expression Q = { { "Ω", 0 } };
|
||||
static const expression V = { { "π", 0 } }; // Does not accept constants.
|
||||
static const expression U = { { "μ", 0 } }; // Only accepts constants.
|
||||
|
||||
// Special functions used in rule creation:
|
||||
//
|
||||
static const auto sx = [ ] ( const expression& a, const expression& b ) { return expression( a, find_opr( "__sx" ), b ); };
|
||||
static const auto zx = [ ] ( const expression& a, const expression& b ) { return expression( a, find_opr( "__zx" ), b ); };
|
||||
static const auto bmask = [ ] ( const expression& a ) { return expression( find_opr( "__bmask" ), a ); };
|
||||
static const auto bcnt = [ ] ( const expression& a ) { return expression( find_opr( "__bcnt" ), a ); };
|
||||
static const auto bcntN = [ ] ( const expression& a, const expression& b ) { return expression( a, find_opr( "__bcntN" ), b ); };
|
||||
|
||||
// All simpilfications:
|
||||
// - Note! Must not contain ( simplified[simplified[x]] == y ).
|
||||
//
|
||||
static std::unordered_map<rule_entry, expression, rule_entry::hash> simplified_form =
|
||||
{
|
||||
// Inverse operations
|
||||
//
|
||||
{ -(-A), A },
|
||||
{ ~(~A), A },
|
||||
{ -(~A), A+1 },
|
||||
{ ~(-A), A-1 },
|
||||
|
||||
// Identity constant
|
||||
//
|
||||
{ A+0, A },
|
||||
{ A-0, A },
|
||||
{ A|A, A },
|
||||
{ A|0, A },
|
||||
{ A&A, A },
|
||||
{ A^0, A },
|
||||
{ A&bmask(A), A },
|
||||
|
||||
// Shift normalization
|
||||
//
|
||||
{ (A>>bcntN(B,A))|(A<<(bcntN(-B,A))), A.ror(B) }, // [imm shift -> imm rotation]
|
||||
{ (A<<bcntN(B,A))|(A>>(bcntN(-B,A))), A.rol(B) }, //
|
||||
{ (A>>bcntN(Q,A))>>bcntN(X,A), A>>(X+Q) }, // merge {imm shift x2}
|
||||
{ (A<<bcntN(Q,A))<<bcntN(X,A), A<<(X+Q) }, //
|
||||
{ (A>>V)|(A<<(bcnt(A)-V)), A.ror(V) }, // [var shift -> var rotation]
|
||||
{ (A<<V)|(A>>(bcnt(A)-V)), A.rol(V) }, //
|
||||
{ A.rol(bcntN(Q,A)), A.rol(Q) }, // normalize {imm rotation}
|
||||
{ A.ror(bcntN(Q,A)), A.ror(Q) }, //
|
||||
{ A>>bcntN(Q,A), {0} }, // noramlize {imm shift}
|
||||
{ A<<bcntN(Q,A), {0} }, //
|
||||
{ (A<<B)>>B, A&(bmask(A)>>B)},
|
||||
{ (A>>B)<<B, A&(bmask(A)<<B)},
|
||||
|
||||
// Special extended rules
|
||||
//
|
||||
{ zx(A,B)>>bcntN(Q,A), {0} }, // take in the real size into account when shifting
|
||||
{ sx(A,B)>>bcntN(Q,A), {-1>>Q} }, // take in the real size into account when shifting
|
||||
|
||||
{ { { A & U }, [ ] ( symbol_map& sym ) // convert mask to __zx
|
||||
{
|
||||
uint64_t mask = sym[ *U.value ].evaluate()->get();
|
||||
auto& in = sym[ *A.value ];
|
||||
auto& out = sym[ *Q.value ];
|
||||
switch ( mask )
|
||||
{
|
||||
case 0: out = { 0 }; break;
|
||||
case 0xFF: out = { expression( in ).resize( 1 ) }; break;
|
||||
case 0xFFFF: out = { expression( in ).resize( 2 ) }; break;
|
||||
case 0xFFFFFFFF: out = { expression( in ).resize( 4 ) }; break;
|
||||
case 0xFFFFFFFFFFFFFFFF: out = { expression( in ).resize( 8 ) }; break;
|
||||
default: return false;
|
||||
}
|
||||
return true;
|
||||
} }, { Q } },
|
||||
|
||||
// Constant result
|
||||
//
|
||||
{ A-A, {0} },
|
||||
{ A+(-A), {0} },
|
||||
{ A&0, {0} },
|
||||
{ A^A, {0} },
|
||||
{ A&(~A), {0} },
|
||||
{ A|bmask(A), bmask(A) },
|
||||
{ A^(~A), bmask(A) },
|
||||
{ A|(~A), bmask(A) },
|
||||
{ A.rol(0), A },
|
||||
{ A.ror(0), A },
|
||||
{ A>>0, A },
|
||||
{ A<<0, A },
|
||||
|
||||
// SUB conversion
|
||||
//
|
||||
{ (~A)+B, ~(A-B) },
|
||||
|
||||
// NEG conversion
|
||||
//
|
||||
{ ~(A+bmask(A)), -A },
|
||||
{ 0-A, -A },
|
||||
|
||||
// Simplify AND OR
|
||||
//
|
||||
{ A&(A|B), A },
|
||||
{ A|(A&B), A },
|
||||
|
||||
// XOR|NAND|NOR -> NOT conversion
|
||||
//
|
||||
{ A^bmask(A), ~A },
|
||||
|
||||
// XOR / OR / AND conversion
|
||||
//
|
||||
{ (~A)&(~B), ~(A|B) },
|
||||
{ (~A)|(~B), ~(A&B) },
|
||||
{ (A|B)&(~(A&B)), A^B },
|
||||
{ (A&(~B))|((~A)&B), A^B },
|
||||
{ ~((~(A|B))|(A&B)), A^B },
|
||||
|
||||
// Prefer SUB over NEG
|
||||
//
|
||||
{ A+(-B), A-B },
|
||||
|
||||
// ADD to OR
|
||||
//
|
||||
{ ((~A)&B)+(A&C), ((~A)&B)|(A&C) }
|
||||
};
|
||||
|
||||
// All alternate forms:
|
||||
// - Note: Both sides should contain the same amount of unknowns.
|
||||
//
|
||||
static std::unordered_map<rule_entry, std::vector<expression>, rule_entry::hash> alternate_forms =
|
||||
{
|
||||
// Convert between SUB and ADD
|
||||
//
|
||||
{ A-B, { A+(-B) }},
|
||||
|
||||
// Convert between bitwise and arithmetic negation
|
||||
//
|
||||
{ ~A, { -(A+1) } },
|
||||
{ -A, { ~(A-1) } },
|
||||
|
||||
// Distribute bitwise operators
|
||||
//
|
||||
{ ~(A^B), { (~A)^B } },
|
||||
{ ~(A^B), { A^(~B) } },
|
||||
{ ~(A&B), { (~A)|(~B) } },
|
||||
{ ~(A|B), { (~A)&(~B) } },
|
||||
{ A&(B|C),{ (A&B)|(A&C) } },
|
||||
{ A|(B&C),{ (A|B)&(A|C) } },
|
||||
{ A&(B^C),{ (A&B)^(A&C) } },
|
||||
{ (A&B)>>C, { (A>>C)&(B>>C) } },
|
||||
{ (A&B)<<C, { (A<<C)&(B<<C) } },
|
||||
{ (A|B)>>C, { (A>>C)|(B>>C) } },
|
||||
{ (A|B)<<C, { (A<<C)|(B<<C) } },
|
||||
{ (A^B)>>C, { (A>>C)^(B>>C) } },
|
||||
{ (A^B)<<C, { (A<<C)^(B<<C) } },
|
||||
{ A^(B|C), {(A&(~(B|C)))|((~A)&(B|C)), (A&(~(C|B)))|((~A)&(C|B))} },
|
||||
|
||||
// All commutative laws.
|
||||
// - Certain instances are commented out since only node #0 and node #1 are reversed,
|
||||
// and since they are not of the same type, matcher will apply the commutative law
|
||||
// automatically anyways to match the other.
|
||||
//
|
||||
{ A+(B+C), { (A+B)+C, (A+C)+B } },
|
||||
//{ (A+B)+C, { A+(B+C), B+(A+C) } },
|
||||
{ A+(B-C), { (A+B)-C, (A-C)+B } },
|
||||
//{ (A-B)+C, { (A-C)-B, A+(C-B) } },
|
||||
{ A-(B+C), { (A-B)-C, (A-C)-B } },
|
||||
{ (A+B)-C, { (A-C)+B, A+(B-C) } },
|
||||
{ A-(B-C), { (A-B)+C, (A+C)-B } },
|
||||
{ (A-B)-C, { (A-C)-B, A-(B+C) } },
|
||||
{ A|(B|C), { (A|B)|C, (A|C)|B } },
|
||||
//{ (A|B)|C, { A|(B|C), B|(A|C) } },
|
||||
{ A&(B&C), { (A&B)&C, (A&C)&B } },
|
||||
//{ (A&B)&C, { A&(B&C), B&(A&C) } },
|
||||
{ A^(B^C), { (A^B)^C, (A^C)^B } },
|
||||
//{ (A^B)^C, { A^(B^C), B^(A^C) } },
|
||||
};
|
||||
|
||||
// A handy helper that invokes enumerator for each entry in the
|
||||
// given map in the order of points and increments the points
|
||||
// and breaks out of the loop if callback returns true.
|
||||
//
|
||||
template<typename T, typename Z>
|
||||
static auto for_each( std::unordered_map<rule_entry, T, rule_entry::hash>& map,
|
||||
const Z& enumerator )
|
||||
{
|
||||
using iterator_type = typename std::unordered_map<rule_entry, T, rule_entry::hash>::iterator;
|
||||
|
||||
std::vector<iterator_type> ref_vec( map.size() );
|
||||
std::iota( ref_vec.begin(), ref_vec.end(), map.begin() );
|
||||
std::sort( ref_vec.begin(), ref_vec.end(), [ ] ( const iterator_type& a, const iterator_type& b ) { return a->first.points > b->first.points; } );
|
||||
|
||||
using ret_type = decltype( enumerator( ref_vec[ 0 ]->first, ref_vec[ 0 ]->second ) );
|
||||
|
||||
if constexpr ( std::is_same_v<ret_type, void> )
|
||||
{
|
||||
for ( auto& it : ref_vec )
|
||||
enumerator( it->first, it->second );
|
||||
}
|
||||
else
|
||||
{
|
||||
for ( auto& it : ref_vec )
|
||||
{
|
||||
if ( enumerator( it->first, it->second ) )
|
||||
{
|
||||
++( *( volatile uint32_t* ) &it->first.points );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if the provided expression tree matches that of a symbolic
|
||||
// tree simplification/alternate form and returns the table to map it
|
||||
// so that they are equivalent.
|
||||
//
|
||||
template<bool bcnt_strict = true>
|
||||
static std::optional<symbol_map> match( const expression& input, const expression& target, const symbol_map& sym_map = {}, uint8_t op_size = 0 )
|
||||
{
|
||||
// If target is a variable.
|
||||
//
|
||||
if ( target.is_variable() )
|
||||
{
|
||||
// If constant, simply compare the values.
|
||||
//
|
||||
if ( target.value->is_constant() )
|
||||
{
|
||||
// Fail if input is not a constant.
|
||||
//
|
||||
if ( !input.is_variable() || !input.value->is_constant() )
|
||||
return {};
|
||||
|
||||
// Determine operation size where possible.
|
||||
//
|
||||
if ( !op_size ) op_size = input.size();
|
||||
|
||||
if ( target.value->get( op_size ) == input.value->get( op_size ) )
|
||||
return { sym_map };
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
// If symbolic map contains the target variable:
|
||||
//
|
||||
auto it = sym_map.find( *target.value );
|
||||
if ( it == sym_map.end() )
|
||||
{
|
||||
// Check special conditions:
|
||||
//
|
||||
if ( target.value->uid == V.value->uid && input.is_constant() )
|
||||
return {};
|
||||
if ( target.value->uid == U.value->uid && !input.is_constant() )
|
||||
return {};
|
||||
|
||||
symbol_map sym_map_new = sym_map;
|
||||
sym_map_new[ *target.value ] = input;
|
||||
return { sym_map_new };
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( it->second == input )
|
||||
return { sym_map };
|
||||
else
|
||||
return {};
|
||||
}
|
||||
}
|
||||
// If input is a variable and target is an expression.
|
||||
//
|
||||
else if ( input.is_variable() )
|
||||
{
|
||||
// If special functor:
|
||||
//
|
||||
if ( target.fn->function == "__bcntN" &&
|
||||
input.is_constant() )
|
||||
{
|
||||
// Find which variable we're calculating this for.
|
||||
// - Referencing unknown variable in simplification condition if assert fail raises.
|
||||
//
|
||||
auto it = sym_map.find( *target[ 1 ].value );
|
||||
fassert( it != sym_map.end() );
|
||||
|
||||
// Find the In/Out operand.
|
||||
//
|
||||
symbol_map sym_map_new = sym_map;
|
||||
expression* exp_out;
|
||||
int8_t sign = +1;
|
||||
if ( target[ 0 ].is_expression() )
|
||||
{
|
||||
// Unknown operation in simplification condition if this is hit.
|
||||
//
|
||||
fassert( target[ 0 ].fn->function == "neg" );
|
||||
sign = -1;
|
||||
exp_out = &sym_map_new[ *target[ 0 ][ 0 ].value ];
|
||||
}
|
||||
else
|
||||
{
|
||||
exp_out = &sym_map_new[ *target[ 0 ].value ];
|
||||
}
|
||||
|
||||
// Calculate number of bits in the variable.
|
||||
//
|
||||
int8_t bit_count = it->second.size() * 8;
|
||||
fassert( bit_count != 0 );
|
||||
|
||||
// If known variable, we're being asked to check if normalized form matches:
|
||||
//
|
||||
if ( exp_out->is_valid() )
|
||||
{
|
||||
// Fail if not constant.
|
||||
//
|
||||
if ( !exp_out->is_constant() )
|
||||
return {};
|
||||
|
||||
int64_t value_a = ( ( bit_count + exp_out->value->get() ) % bit_count );
|
||||
int64_t value_b = ( ( bit_count + sign * input.value->get() ) % bit_count );
|
||||
if ( value_a == value_b )
|
||||
return { sym_map };
|
||||
else
|
||||
return {};
|
||||
}
|
||||
// If unknown variable, it's asking for normalized form:
|
||||
//
|
||||
else
|
||||
{
|
||||
// Skip if already normalized
|
||||
//
|
||||
int64_t value = sign * input.value->get();
|
||||
if ( 0 <= value && value < bit_count && bcnt_strict )
|
||||
return {};
|
||||
|
||||
// Write normalized value and indicate success.
|
||||
//
|
||||
*exp_out = variable( ( ( bit_count + value ) % bit_count ), op_size );
|
||||
return { sym_map_new };
|
||||
}
|
||||
}
|
||||
// If constant maps to expression, try remapping
|
||||
// and checking if it evaluates to the same value.
|
||||
//
|
||||
else if ( input.is_constant() )
|
||||
{
|
||||
expression copy = target;
|
||||
copy.remap_symbols( sym_map );
|
||||
if( copy.evaluate() == input.value )
|
||||
return { sym_map };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
// If both are expressions.
|
||||
//
|
||||
else
|
||||
{
|
||||
// If operators mismatch:
|
||||
//
|
||||
if ( input.fn != target.fn )
|
||||
{
|
||||
return {};
|
||||
}
|
||||
// If matching operators, compare operands:
|
||||
//
|
||||
else
|
||||
{
|
||||
// Determine expected size:
|
||||
//
|
||||
op_size = input.size();
|
||||
|
||||
// If unary operator:
|
||||
//
|
||||
std::optional<symbol_map> result;
|
||||
if ( input.fn->is_unary )
|
||||
{
|
||||
result = match<bcnt_strict>( input[ 0 ], target[ 0 ], sym_map, op_size );
|
||||
}
|
||||
// If binary operator:
|
||||
//
|
||||
else
|
||||
{
|
||||
// Check if operands match, in order:
|
||||
//
|
||||
result = match<bcnt_strict>( input[ 0 ], target[ 0 ], sym_map, op_size );
|
||||
if ( result.has_value() ) result = match<bcnt_strict>( input[ 1 ], target[ 1 ], result.value(), op_size );
|
||||
|
||||
// Otherwise check if operator is commutative and operands match in reverse:
|
||||
//
|
||||
if ( !result.has_value() && input.fn->commutative == +1 )
|
||||
{
|
||||
result = match<bcnt_strict>( input[ 0 ], target[ 1 ], sym_map, op_size );
|
||||
if ( result.has_value() ) result = match<bcnt_strict>( input[ 1 ], target[ 0 ], result.value(), op_size );
|
||||
}
|
||||
}
|
||||
|
||||
// Return the final result.
|
||||
//
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
template<bool bcnt_strict = true>
|
||||
static std::optional<symbol_map> match( const expression& input, const rule_entry& rule )
|
||||
{
|
||||
// All variables should be of the same size.
|
||||
//
|
||||
fassert( input.is_normalized() );
|
||||
|
||||
// Check if equivalent, if not return invalid expression.
|
||||
//
|
||||
auto sym_map = match<bcnt_strict>( input, rule.base_expression );
|
||||
if ( !sym_map )
|
||||
return {};
|
||||
if ( rule.extension && !rule.extension( *sym_map ) )
|
||||
return {};
|
||||
return sym_map;
|
||||
}
|
||||
|
||||
// Transforms between equivalent expression trees.
|
||||
//
|
||||
static expression remap( const expression& input, const symbol_map& sym_map, const expression& to )
|
||||
{
|
||||
// Remap symbol.
|
||||
//
|
||||
expression new_expression = to;
|
||||
new_expression.remap_symbols( sym_map );
|
||||
|
||||
// Remove any remaining special instructions
|
||||
//
|
||||
std::function<void( expression& )> remove_special = [ & ] ( expression& r )
|
||||
{
|
||||
if ( r.fn && r.fn->function[ 0 ] == '_' )
|
||||
{
|
||||
if ( auto val = r.evaluate() )
|
||||
{
|
||||
r = *val;
|
||||
return;
|
||||
}
|
||||
}
|
||||
for ( auto& op : r.operands )
|
||||
remove_special( op );
|
||||
};
|
||||
remove_special( new_expression );
|
||||
return new_expression.resize( input.size() );
|
||||
}
|
||||
|
||||
template<bool bcnt_strict = true>
|
||||
static std::optional<expression> apply( const expression& input, const rule_entry& rule, const expression& target )
|
||||
{
|
||||
auto sym_map = match<bcnt_strict>( input, rule );
|
||||
if ( !sym_map )
|
||||
return {};
|
||||
return remap( input, sym_map.value(), target );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
#pragma once
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include "rules.hpp"
|
||||
#include "expression.hpp"
|
||||
#include "variable.hpp"
|
||||
#include "operators.hpp"
|
||||
|
||||
namespace vtil::symbolic
|
||||
{
|
||||
// Tries to simplify the given symbolic expression as much as possible.
|
||||
//
|
||||
static std::optional<expression> try_simplify( const expression& input, bool skip_top_level = false )
|
||||
{
|
||||
// Assert we received a valid expression.
|
||||
//
|
||||
fassert( input.is_valid() );
|
||||
|
||||
// If expression is already in the simplest form possible, return as is.
|
||||
//
|
||||
if ( input.is_simplest_form )
|
||||
return {};
|
||||
|
||||
// Simplify children.
|
||||
//
|
||||
expression exp = input;
|
||||
bool simplified_child = false;
|
||||
for ( auto& op : exp.operands )
|
||||
{
|
||||
if ( op.is_simplest_form )
|
||||
continue;
|
||||
if ( auto r = try_simplify( op ) )
|
||||
simplified_child = true, op = r.value();
|
||||
op.declare_simple();
|
||||
}
|
||||
|
||||
// Try evaluating current expression, if we could
|
||||
// return it as is.
|
||||
//
|
||||
if ( auto eval = input.evaluate() )
|
||||
return eval.value();
|
||||
|
||||
// If top level is to be simplified:
|
||||
//
|
||||
if ( !skip_top_level )
|
||||
{
|
||||
// For each alternate form:
|
||||
//
|
||||
size_t complexity_0 = exp.complexity();
|
||||
bool success = rules::for_each( rules::alternate_forms, [ & ] ( const rules::rule_entry& rule, const std::vector<expression>& forms )
|
||||
{
|
||||
// If we match the rules:
|
||||
//
|
||||
if ( auto sym_map = rules::match( exp, rule ) )
|
||||
{
|
||||
// For each form:
|
||||
//
|
||||
for ( auto& form : forms )
|
||||
{
|
||||
// Try simplifying, if we could not, continue onto next one.
|
||||
//
|
||||
auto new_exp = try_simplify( rules::remap( exp, *sym_map, form ), true );
|
||||
if ( !new_exp ) continue;
|
||||
|
||||
// If complexity was reduced:
|
||||
//
|
||||
size_t complexity_1 = new_exp->complexity();
|
||||
if ( complexity_1 < complexity_0 )
|
||||
{
|
||||
// Recurse, write the result at exp, indicate success.
|
||||
//
|
||||
exp = try_simplify( *new_exp ).value_or( *new_exp ).declare_simple();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} );
|
||||
if ( success ) return exp;
|
||||
}
|
||||
|
||||
// For each simplified form:
|
||||
//
|
||||
bool success = rules::for_each( rules::simplified_form, [ & ] ( const rules::rule_entry& rule, const expression& form )
|
||||
{
|
||||
// If we could apply the simplification rule:
|
||||
//
|
||||
if ( auto new_exp = rules::apply( exp, rule, form ) )
|
||||
{
|
||||
// Recurse, write the result at exp, indicate success.
|
||||
//
|
||||
exp = try_simplify( *new_exp, skip_top_level ).value_or( *new_exp ).declare_simple();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} );
|
||||
if ( success ) return exp;
|
||||
|
||||
// Try evaluating new expression, if we could
|
||||
// return it as is.
|
||||
//
|
||||
if ( auto eval = exp.evaluate() )
|
||||
return eval.value();
|
||||
|
||||
// If we simplified a child, still declare simple and return.
|
||||
//
|
||||
if ( simplified_child )
|
||||
return exp.declare_simple();
|
||||
return {};
|
||||
}
|
||||
static expression simplify( expression input )
|
||||
{
|
||||
// TODO: Fix equivalent registers of different size being in the variable
|
||||
|
||||
// Fail if input is invalid.
|
||||
//
|
||||
if ( !input.is_valid() )
|
||||
return {};
|
||||
|
||||
// Explicitly resize the expression to output size.
|
||||
//
|
||||
input.resize( input.size() );
|
||||
|
||||
// Try simplifying the expression, and declare simple.
|
||||
//
|
||||
if ( auto r = try_simplify( input ) )
|
||||
input = r.value();
|
||||
return input.resize( input.size() ).declare_simple();
|
||||
}
|
||||
|
||||
// Checks whether the two given expressions are equivalent in a more reliable
|
||||
// fashion when compared to simply invoking expression::operator==(...)
|
||||
//
|
||||
static bool is_equivalent( const expression& a, const expression& b )
|
||||
{
|
||||
// If naive-comparison returns equivalent, return so.
|
||||
//
|
||||
if ( a == b )
|
||||
return true;
|
||||
|
||||
// Try matching the simplification of an expression that
|
||||
// would be zero if a and be were equivalent instead
|
||||
// otherwise to cause a much more complex evaluation.
|
||||
//
|
||||
return simplify( a - b ) == variable{ 0 };
|
||||
}
|
||||
};
|
||||
|
|
@ -1,407 +0,0 @@
|
|||
#pragma once
|
||||
#define SYMEX_CONST_SIZE_DEFAULT(x) 0
|
||||
|
||||
#include <string>
|
||||
#include <codecvt>
|
||||
#include <type_traits>
|
||||
#include "..\arch\operands.hpp"
|
||||
#include "..\arch\instruction_set.hpp"
|
||||
#include "..\routine\basic_block.hpp"
|
||||
#include "..\misc\format.hpp"
|
||||
|
||||
namespace vtil::symbolic
|
||||
{
|
||||
// Dictionary for the names of reserved unique identifiers. (For simplifier)
|
||||
//
|
||||
static const std::wstring uid_reserved_dictionary =
|
||||
L"ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρστυφχψω"
|
||||
L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
// Unique identifier for a variable.
|
||||
//
|
||||
struct unique_identifier
|
||||
{
|
||||
// Conversion between the internal unicode type and the UTF8 output expected.
|
||||
//
|
||||
using utf_cvt_t = std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>;
|
||||
|
||||
// Unique name of the variable.
|
||||
//
|
||||
std::string name;
|
||||
|
||||
// Origin of the variable if relevant.
|
||||
//
|
||||
ilstream_const_iterator origin = {};
|
||||
std::optional<register_view> register_id = {};
|
||||
std::optional<std::pair<register_view, int64_t>> memory_id = {};
|
||||
uint32_t memory_base_idx = 0;
|
||||
bool branch_dependant = false;
|
||||
|
||||
// Default constructors for simple identifiers.
|
||||
//
|
||||
unique_identifier() {}
|
||||
unique_identifier( const std::string& name ) : name( name ) { fassert( is_valid() ); }
|
||||
|
||||
// Constructors for unique identifiers created from stream iterators.
|
||||
//
|
||||
unique_identifier( const register_view& base, int64_t offset, ilstream_const_iterator at = {} )
|
||||
{
|
||||
assign( base, offset ).bind( at );
|
||||
}
|
||||
unique_identifier( const register_view& reg, ilstream_const_iterator at = {} )
|
||||
{
|
||||
assign( reg ).bind( at );
|
||||
}
|
||||
unique_identifier( ilstream_const_iterator origin, int operand_index )
|
||||
{
|
||||
// Identifier for memory:
|
||||
//
|
||||
if ( operand_index == -1 )
|
||||
{
|
||||
fassert( origin->base->accesses_memory() );
|
||||
auto [mem_base, mem_loc] = origin->get_mem_loc();
|
||||
assign( mem_base, mem_loc ).bind( origin );
|
||||
}
|
||||
// Identifier for register/temporary:
|
||||
//
|
||||
else
|
||||
{
|
||||
fassert( origin->operands[ operand_index ].is_register() );
|
||||
assign( origin->operands[ operand_index ].reg ).bind( origin );
|
||||
}
|
||||
}
|
||||
|
||||
// Refreshes the unique identifier if it's bound to a register value or a pointer.
|
||||
//
|
||||
unique_identifier& refresh()
|
||||
{
|
||||
if ( register_id.has_value() )
|
||||
{
|
||||
name = register_id->to_string();
|
||||
|
||||
if ( !origin.is_end() )
|
||||
{
|
||||
name += '@';
|
||||
if ( origin->vip != invalid_vip )
|
||||
name += format::str( "%llx", origin->vip );
|
||||
else
|
||||
name += format::str( "%llx", origin.container->entry_vip ) + "#" + std::to_string( std::distance( origin.container->begin(), origin ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
name += origin.is_begin() ? "?" : "*";
|
||||
}
|
||||
}
|
||||
else if( memory_id.has_value() )
|
||||
{
|
||||
if ( memory_id->first.base == X86_REG_RSP )
|
||||
{
|
||||
name = memory_id->second >= 0 ? "arg" : "var";
|
||||
name += format::hex( abs( memory_id->second ) );
|
||||
if( memory_base_idx != 0 )
|
||||
name += format::str( "#%d", memory_base_idx );
|
||||
}
|
||||
else
|
||||
{
|
||||
name = "[";
|
||||
name += memory_id->first.base.to_string();
|
||||
name += "+" + format::hex( memory_id->second ) + "]";
|
||||
}
|
||||
|
||||
if ( !origin.is_end() )
|
||||
{
|
||||
name += '@';
|
||||
if ( origin->vip != invalid_vip )
|
||||
name += format::str( "%llx", origin->vip );
|
||||
else
|
||||
name += format::str( "%llx", origin.container->entry_vip ) + "#" + std::to_string( std::distance( origin.container->begin(), origin ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
name += origin.is_begin() ? "?" : "*";
|
||||
}
|
||||
}
|
||||
if( branch_dependant )
|
||||
name += "...";
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Assigns unique identifier the value of a register or a pointer.
|
||||
//
|
||||
unique_identifier& assign( const register_view& reg )
|
||||
{
|
||||
register_id = { reg };
|
||||
memory_id = {};
|
||||
return refresh();
|
||||
}
|
||||
unique_identifier& assign( const register_view& base, int64_t offset )
|
||||
{
|
||||
register_id = {};
|
||||
memory_id = { base, offset };
|
||||
return refresh();
|
||||
}
|
||||
|
||||
// Binds/unbinds the identifier from/to the origin.
|
||||
//
|
||||
unique_identifier& unbind() { origin = {}; return refresh(); }
|
||||
unique_identifier& bind( ilstream_const_iterator it )
|
||||
{
|
||||
origin = it;
|
||||
if ( it.is_valid() && memory_id )
|
||||
memory_base_idx = it.is_end() ? it.container->sp_index : it->sp_index;
|
||||
return refresh();
|
||||
}
|
||||
|
||||
// Declares value branch dependant/not.
|
||||
//
|
||||
unique_identifier& set_branch_dependency( bool v = true )
|
||||
{
|
||||
if ( branch_dependant != v )
|
||||
{
|
||||
branch_dependant = v;
|
||||
return refresh();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Helpers used to resolve the actual operand being traced.
|
||||
//
|
||||
bool is_valid() const { return name.size() && !iswdigit( name[ 0 ] ); }
|
||||
bool is_reserved() const { return name.size() == 1 && uid_reserved_dictionary.find( name[ 0 ] ) != std::wstring::npos; }
|
||||
bool is_memory() const { return is_valid() && memory_id.has_value(); }
|
||||
bool is_register() const { return is_valid() && register_id.has_value(); }
|
||||
bool is_arbitrary() const { return is_valid() && ( memory_id.has_value() || register_id.has_value() ); }
|
||||
|
||||
// Returns the pointer associated with the variable.
|
||||
//
|
||||
auto get_mem() const
|
||||
{
|
||||
fassert( is_memory() );
|
||||
return memory_id.value();
|
||||
}
|
||||
|
||||
// Returns the register that is associated with the variable.
|
||||
//
|
||||
register_view get_reg() const
|
||||
{
|
||||
fassert( is_register() );
|
||||
return register_id.value();
|
||||
}
|
||||
|
||||
// Conversion to human readable format.
|
||||
//
|
||||
std::string to_string() const { return name; }
|
||||
|
||||
// Simple comparison operators.
|
||||
//
|
||||
bool operator==( const unique_identifier& o ) const
|
||||
{
|
||||
return name == o.name && origin == o.origin;
|
||||
}
|
||||
bool operator<( const unique_identifier& o ) const { return name < o.name; }
|
||||
bool operator!=( const unique_identifier& o ) const { return name != o.name; }
|
||||
};
|
||||
|
||||
// Describes a variable that will be used in a symbolic expression.
|
||||
//
|
||||
struct variable
|
||||
{
|
||||
// A unique identifier for the variable, if left empty
|
||||
// this variable will be treated as a constant value.
|
||||
//
|
||||
unique_identifier uid;
|
||||
|
||||
// If constant, the value that is being represented
|
||||
// by this variable. Do not ever access directly (not via .get).
|
||||
//
|
||||
union
|
||||
{
|
||||
uint64_t _u64;
|
||||
int64_t _i64;
|
||||
};
|
||||
|
||||
// Size of the variable, used for both constants and UID-bound variables.
|
||||
// - If zero, implies any size.
|
||||
//
|
||||
uint8_t size;
|
||||
|
||||
// Default constructor, will make an invalid varaible.
|
||||
//
|
||||
variable() : size( 0 ) {}
|
||||
|
||||
// Constructor for uniquely variables.
|
||||
//
|
||||
variable( const std::string& uid, uint8_t size ) : uid( uid ), size( size ) { fassert( is_valid() ); }
|
||||
variable( const unique_identifier& uid, uint8_t size ) : uid( uid ), size( size ) { fassert( is_valid() ); }
|
||||
variable( ilstream_const_iterator origin, int operand_index )
|
||||
{
|
||||
// If operand is an immediate or a register:
|
||||
//
|
||||
if ( operand_index != -1 )
|
||||
{
|
||||
// If immediate, assign constant:
|
||||
//
|
||||
if ( origin->operands[ operand_index ].is_immediate() )
|
||||
_u64 = origin->operands[ operand_index ].u64;
|
||||
// If register, generate unique identifier:
|
||||
//
|
||||
else
|
||||
uid = { origin, operand_index };
|
||||
|
||||
// Assing operand size as the variable size.
|
||||
//
|
||||
size = origin->operands[ operand_index ].size();
|
||||
}
|
||||
// If operand is a stack pointer:
|
||||
//
|
||||
else
|
||||
{
|
||||
// Generate a unique identifier and assign the size.
|
||||
//
|
||||
uid = { origin, operand_index };
|
||||
size = origin->access_size();
|
||||
}
|
||||
|
||||
fassert( is_valid() );
|
||||
}
|
||||
|
||||
// Constructor for variables that represent constant values.
|
||||
//
|
||||
template<typename T, std::enable_if_t<std::is_arithmetic_v<T>, int> = 0>
|
||||
variable( T imm, uint8_t size = SYMEX_CONST_SIZE_DEFAULT( T ) ) : size( size )
|
||||
{
|
||||
fassert( is_valid() );
|
||||
|
||||
// If a signed type was passed, sign extend, otherwise zero extend before storing.
|
||||
//
|
||||
if constexpr ( std::is_signed_v<T> )
|
||||
_i64 = imm;
|
||||
else
|
||||
_u64 = imm;
|
||||
}
|
||||
|
||||
// Simple helpers to determine the type of the variable.
|
||||
//
|
||||
bool is_valid() const { return size == 0 || size == 1 || size == 2 || size == 4 || size == 8; }
|
||||
bool is_symbolic() const { return uid.is_valid(); }
|
||||
bool is_constant() const { return !uid.is_valid(); }
|
||||
|
||||
// Wrappers around unique_identifer:: helpers used to resolve the actual operand being traced.
|
||||
//
|
||||
bool is_memory() const { return uid.is_memory(); }
|
||||
bool is_register() const { return uid.is_register(); }
|
||||
bool is_arbitrary() const { return uid.is_arbitrary(); }
|
||||
auto get_mem() const { return uid.get_mem(); }
|
||||
register_view get_reg() const { register_view reg = uid.get_reg(); fassert( size == reg.size ); return reg; }
|
||||
auto& unbind() { uid.unbind(); return *this; }
|
||||
auto& bind( ilstream_const_iterator it ) { uid.bind( it ); return *this; }
|
||||
|
||||
// Resizes the variable.
|
||||
//
|
||||
variable& resize( uint8_t new_size, bool sign_extend )
|
||||
{
|
||||
if ( is_constant() )
|
||||
_u64 = sign_extend ? get<true>( new_size ) : get<false>( new_size );
|
||||
else
|
||||
fassert( size >= new_size && new_size != 0 );
|
||||
|
||||
size = new_size;
|
||||
if ( is_register() ) uid.register_id->size = new_size;
|
||||
fassert( is_valid() );
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Instead of using the size variable as is, this function
|
||||
// calculates minimum equivalent size if the constant is an
|
||||
// any-size special.
|
||||
//
|
||||
uint8_t calc_size( bool sign ) const
|
||||
{
|
||||
if ( size || !is_constant() )
|
||||
return size;
|
||||
|
||||
if ( sign )
|
||||
{
|
||||
if ( get<true>( 1 ) == _i64 ) return 1;
|
||||
else if ( get<true>( 2 ) == _i64 ) return 2;
|
||||
else if ( get<true>( 4 ) == _i64 ) return 4;
|
||||
else if ( get<true>( 8 ) == _i64 ) return 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( get( 1 ) == _u64 ) return 1;
|
||||
else if ( get( 2 ) == _u64 ) return 2;
|
||||
else if ( get( 4 ) == _u64 ) return 4;
|
||||
else if ( get( 8 ) == _u64 ) return 8;
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
// Getter for value:
|
||||
//
|
||||
template<bool sign = false>
|
||||
auto get( uint8_t new_size = 0 ) const
|
||||
{
|
||||
fassert( is_constant() );
|
||||
|
||||
uint8_t out_size = size;
|
||||
if ( out_size == 0 || ( new_size != 0 && new_size < out_size ) )
|
||||
out_size = new_size;
|
||||
|
||||
if constexpr ( sign )
|
||||
{
|
||||
switch ( out_size )
|
||||
{
|
||||
case 0: case 8: return _i64; break;
|
||||
case 1: return ( int64_t ) *( int8_t* ) &_i64; break;
|
||||
case 2: return ( int64_t ) *( int16_t* ) &_i64; break;
|
||||
case 4: return ( int64_t ) *( int32_t* ) &_i64; break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch ( out_size )
|
||||
{
|
||||
case 0: case 8: return _u64; break;
|
||||
case 1: return ( uint64_t ) *( uint8_t* ) &_u64; break;
|
||||
case 2: return ( uint64_t ) *( uint16_t* ) &_u64; break;
|
||||
case 4: return ( uint64_t ) *( uint32_t* ) &_u64; break;
|
||||
}
|
||||
}
|
||||
unreachable();
|
||||
}
|
||||
|
||||
// Converts the variable into human-readable format.
|
||||
//
|
||||
std::string to_string() const
|
||||
{
|
||||
if ( !is_symbolic() )
|
||||
return format::hex( get<true>() );
|
||||
else if ( size == 0 )
|
||||
return uid.to_string();
|
||||
else
|
||||
return uid.to_string() + format::suffix_map[ size ];
|
||||
}
|
||||
|
||||
// Basic comparison operators.
|
||||
//
|
||||
bool operator==( const variable& o ) const
|
||||
{
|
||||
if ( is_constant() )
|
||||
return o.is_constant() && get( o.size ) == o.get( size );
|
||||
else
|
||||
return o.is_symbolic() && uid == o.uid && size == o.size;
|
||||
}
|
||||
bool operator!=( const variable& o ) const { return !operator==( o ); }
|
||||
bool operator<( const variable& o ) const
|
||||
{
|
||||
if ( is_symbolic() != o.is_symbolic() )
|
||||
return o.is_symbolic();
|
||||
|
||||
if ( is_symbolic() )
|
||||
return uid < o.uid;
|
||||
else
|
||||
return get() < o.get();
|
||||
}
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue