2026-07-10 10:38:57 +02:00

366 lines
11 KiB
Python

import logging
import struct
from ScEpTIC import tools
from ScEpTIC.exceptions import MemoryException
class Value:
"""
AST node for data representation (immediate, register data, etc)
"""
_vmstate = None
def __init__(self, value_class, operand_value, value_type):
self.value_class = value_class
self.value = operand_value
self.type = value_type
def __repr__(self):
return 'Value({}, {}, {})'.format(self.value_class, self.value, self.type)
def __str__(self):
if self.type is None:
return str(self.value)
return 'Value: ({}) {}'.format(self.type, self.value)
def __len__(self):
return len(self.type)
def is_llvm_string(self):
try:
return self.value_class == 'address' and '@.str' in self.value.element.value
except:
return False
def get_val(self):
"""
Retrieve and returns the value represented by the object.
"""
if self._vmstate is None:
raise MemoryException('VM Status not initialized!')
if self.value_class == 'immediate':
value = self._resolve_immediate()
elif self.value_class == 'array_val':
value = self._resolve_array_val()
elif self.value_class == 'struct_val':
value = self._resolve_array_val()
elif self.value_class == 'virtual_reg':
value = self._vmstate.register_file.read(self.value)
elif self.value_class == 'global_var':
# Function pointer -> same initial token of global_vars
# If function exists -> return value
if self.value in self._vmstate.functions:
value = self.value
else:
value = self._vmstate.memory.gst.get_symbol_address(self.value)
elif self.value_class == 'vector':
raise NotImplementedError('Vector operations not supported at the moment. Please compile with a different target.')
elif self.value_class == 'address':
# GetElementPointerOperation has method get_val that resolves the correct value
value = self.value.get_val()
elif self.value_class == 'conversion':
# ConversionOperation has method get_val that resolves the correct value
value = self.value.get_val()
elif self.value_class == 'metadata':
# appears only in @llvm.dbg functions, so no need to resolve that address
raise ValueError('Metadata should not be present in running functions.')
logging.info('[Value] Resolved value of {} with {}.'.format(self.value, value))
return value
def resolve_memory_tag(self, elements):
"""
Resolves and returns the memory tag of the targeted element
"""
if self.value_class == 'immediate':
return self._resolve_immediate()
elif self.value_class == 'virtual_reg':
return elements[self.value].resolve_memory_tag(elements)
elif self.value_class == 'global_var':
return self.value
elif self.value_class == 'address':
return self.value.resolve_memory_tag(elements)
elif self.value_class == 'conversion':
return self.value.resolve_memory_tag(elements)
raise NotImplementedError(f"resolve_memory_tag() not implemented for value class {self.value_class}")
def resolve_memory_tag_dependency(self, elements):
if self.value_class == 'immediate':
return []
elif self.value_class == 'virtual_reg':
return elements[self.value].resolve_memory_tag_dependency(elements)
elif self.value_class == 'global_var':
return [self.value]
elif self.value_class == 'address':
return self.value.resolve_memory_tag_dependency(elements)
elif self.value_class == 'conversion':
return self.value.resolve_memory_tag_dependency(elements)
raise NotImplementedError(f"resolve_memory_tag() not implemented for value class {self.value_class}")
def resolve_memory_address_chain(self, elements):
"""
Returns a list of all the instructions required to get the address of the targeted element(s)
"""
if self.value_class == 'immediate':
return []
elif self.value_class == 'virtual_reg':
return [elements[self.value].resolve_memory_address_chain(elements)]
elif self.value_class == 'global_var':
return []
elif self.value_class == 'address':
return [self.value.resolve_memory_address_chain(elements)]
elif self.value_class == 'conversion':
return [self.value.resolve_memory_address_chain(elements)]
raise NotImplementedError(f"resolve_memory_address_chain() not implemented for value class {self.value_class}")
def get_input_lookup(self):
"""
Returns the input lookup information for the current Value object.
"""
if self._vmstate.input_lookup_enabled:
if self.value_class == 'virtual_reg':
return self._vmstate.register_file.get_input_lookup(self.value)
elif self.value_class == 'address':
return self.value.get_input_lookup()
elif self.value_class == 'conversion':
return self.value.get_input_lookup()
return tools.build_input_lookup_data(None, None)
def get_uses(self):
"""
Returns a list containing the names of the registers used by this value.
(used by register allocation)
"""
if self.value_class == 'virtual_reg':
return [self.value]
elif self.value_class == 'address':
return self.value.get_uses()
elif self.value_class == 'conversion':
return self.value.get_uses()
return []
def get_memory_uses(self):
if self.value_class == 'global_var':
return [self.value]
def get_defs(self):
"""
Returns a list of registers defined by this instruction.
(used by register allocation)
"""
return []
def replace_reg_name(self, old_reg_name, new_reg_name):
"""
Replaces the name of a register contained in this Value object.
(used by register allocation)
"""
if self.value_class == 'virtual_reg':
if self.value == old_reg_name:
self.value = new_reg_name
elif self.value_class == 'address':
self.value.replace_reg_name(old_reg_name, new_reg_name)
elif self.value_class == 'conversion':
self.value.replace_reg_name(old_reg_name, new_reg_name)
def _resolve_immediate(self):
"""
Resolves the value of a immediate.
"""
if self.value is None:
return None
if self.value == 'null':
return 0
if self.value == 'true':
return 1
if self.value == 'false':
return 0
try:
return int(self.value)
except ValueError:
pass
try:
return float(self.value)
except ValueError:
pass
if 'e+' in self.value:
return float(self.value)
if '0x' in self.value:
# self.value[2:] to remove 0x
# each value is converted as a double from llvm, even if is a float
# NB: target architecture could be in little endian (e in target_datalayout)
# or big endian (E in target_datalayout). The conversion in the llvm ir stays the same,
# independently of the type of endianness used by the backend compiler.
# So the endianness conversion stays ! (network - big-endian)
return struct.unpack('!d', bytes.fromhex(self.value[2:]))[0]
if self.value == 'zeroinitializer':
composition = self.type.get_memory_composition(True)
for i in range(0, len(composition)):
composition[i] = 0
return composition
return self.value
def _resolve_array_val(self):
"""
Resolves the value of array initialization.
"""
flat_values = []
tools.inf_depth_lst_flat(self.value, flat_values)
for i in range(0, len(flat_values)):
flat_values[i] = flat_values[i].get_val()
return flat_values
@staticmethod
def convert_sint_to_bin(val, bits):
"""
Converts a signed integer to its binary representation, using a certain number of bits
"""
format_str = '{:0'+str(bits)+'b}'
if val < 0:
# all 1s
mask = int('1' * bits, 2)
val = format_str.format(val & mask)
else:
val = format_str.format(val)
# return correct bit dimension
return val[-bits:]
@staticmethod
def convert_uint_to_bin(val, bits):
"""
Converts an unsigned integer to its binary representation.
For how data is represented, it is the same as converting a signed integer to binary.
"""
return Value.convert_sint_to_bin(val, bits)
@staticmethod
def convert_bin_to_uint(val):
"""
Converts a number in binary format to its equivalent unsigned decimal.
"""
return int(val, 2)
@staticmethod
def convert_bin_to_sint(val):
"""
Converts a number in binary format to its equivalent signed decimal.
"""
if val[0] == '0':
return int(val, 2)
if len(val) == 1:
return int(val, 2)
return int(val[1:], 2) - (2 ** (len(val) - 1))
@staticmethod
def convert_sint_to_uint(val, bits):
"""
Converts a number in signed integer form to its equivalent unsigned integer.
"""
val = Value.convert_sint_to_bin(val, bits)
return Value.convert_bin_to_uint(val)
@staticmethod
def convert_uint_to_sint(val, bits):
"""
Converts a number in unsigned integer form to its equivalent signed integer.
"""
val = Value.convert_uint_to_bin(val, bits)
return Value.convert_bin_to_sint(val)
@staticmethod
def convert_sint_to_sint(val, bits):
"""
Convert a signed integer to a signed integer, using a maximum number of bits.
"""
val = Value.convert_sint_to_bin(val, bits)
return Value.convert_bin_to_sint(val)