""" Cool idea to load actual object code into memory and execute it from python using ctypes Credits for idea: Luke Campagnola """ import inspect import sys import logging import ctypes from ..arch import get_current_arch from .. import ir from ..binutils import debuginfo, layout from ..binutils.linker import link from .memory_page import MemoryPage logger = logging.getLogger("codepage") debug_type_name_mapping = { "int": ctypes.c_int, "char": ctypes.c_int, # TODO: how to handle this? "long": ctypes.c_long, "int8_t": ctypes.c_int8, "int16_t": ctypes.c_int16, "int32_t": ctypes.c_int32, "int64_t": ctypes.c_int64, "uint8_t": ctypes.c_uint8, "uint16_t": ctypes.c_uint16, "uint32_t": ctypes.c_uint32, "uint64_t": ctypes.c_uint64, "void": ctypes.c_int, # TODO: what to do? "double": ctypes.c_double, "float": ctypes.c_float, "bool": ctypes.c_int, "byte": ctypes.c_int, } def get_ctypes_type(debug_type): if isinstance(debug_type, debuginfo.DebugBaseType): return debug_type_name_mapping[debug_type.name] elif isinstance(debug_type, debuginfo.DebugPointerType): if isinstance(debug_type.pointed_type, debuginfo.DebugStructType): # TODO: treat struct pointers as void pointers for now. # TODO: fix this? return ctypes.c_voidp else: return ctypes.POINTER(get_ctypes_type(debug_type.pointed_type)) elif isinstance(debug_type, debuginfo.DebugArrayType): element_type = get_ctypes_type(debug_type.element_type) return ctypes.ARRAY(element_type, debug_type.size) elif debug_type is None: return elif isinstance(debug_type, type): mapping = {int: ctypes.c_int, float: ctypes.c_double} return mapping[debug_type] elif isinstance(debug_type, ir.BasicTyp): mapping = { ir.f32: ctypes.c_float, ir.f64: ctypes.c_double, ir.i32: ctypes.c_int32, ir.i64: ctypes.c_int64, # TODO: which one of 32 and 64 is int? } return mapping[debug_type] else: # pragma: no cover raise NotImplementedError(str(debug_type) + str(type(debug_type))) class Mod: """ Container for machine code """ def __init__(self, obj, imports=None): code_size = obj.get_section("code").size data_size = obj.get_section("data").size if not obj.debug_info: raise ValueError( 'Unable to load "{}"' " because it does not contain debug info.".format(obj) ) # Create a code page into memory: self._code_page = MemoryPage(code_size) self._data_page = MemoryPage(data_size) # Create callback pointers if any: imports = imports or {} # Store reference on self to keep reference to object: self._import_symbols = [] extra_symbols = {} for name, imp_obj in imports.items(): if callable(imp_obj): signature = inspect.signature(imp_obj) if signature.return_annotation is inspect._empty: raise ValueError( '"{}" requires return type annotations'.format(name) ) return_type = signature.return_annotation argument_types = [ p.annotation for p in signature.parameters.values() ] restype = get_ctypes_type(return_type) argtypes = [get_ctypes_type(a) for a in argument_types] ftype = ctypes.CFUNCTYPE(restype, *argtypes) callback = ftype(imp_obj) logger.debug("Import name %s", name) self._import_symbols.append((name, callback, ftype)) extra_symbols[name] = ctypes.cast( callback, ctypes.c_void_p ).value elif isinstance(imp_obj, MemoryPage): self._import_symbols.append((name, imp_obj)) extra_symbols[name] = imp_obj.addr else: raise ValueError( "Cannot import {} of type {}".format(name, type(imp_obj)) ) # Link to e.g. apply offset to global literals memory_layout = layout.Layout() layout_code_mem = layout.Memory("codepage") layout_code_mem.location = self._code_page.addr layout_code_mem.size = code_size layout_code_mem.add_input(layout.Section("code")) memory_layout.add_memory(layout_code_mem) layout_data_mem = layout.Memory("datapage") layout_data_mem.location = self._data_page.addr layout_data_mem.size = data_size layout_data_mem.add_input(layout.Section("data")) memory_layout.add_memory(layout_data_mem) # Link the object into memory: obj = link( [obj], layout=memory_layout, debug=True, extra_symbols=extra_symbols, ) # Load the code into the page: code = bytes(obj.get_section("code").data) assert len(code) <= code_size self._code_page.write(code) data = bytes(obj.get_section("data").data) assert len(data) <= data_size self._data_page.write(data) # TODO: we might have more sections! # Get a function pointer for function in obj.debug_info.functions: function_name = function.name # Determine the function type: restype = get_ctypes_type(function.return_type) argtypes = [get_ctypes_type(a.typ) for a in function.arguments] logger.debug("function sig %s %s", restype, argtypes) ftype = ctypes.CFUNCTYPE(restype, *argtypes) # Create a function pointer: vaddress = obj.get_symbol_id_value(function.begin.symbol_id) fpointer = ftype(vaddress) # Set the attribute: setattr(self, function_name, fpointer) # Get a variable pointers for variable in obj.debug_info.variables: variable_name = variable.name assert isinstance(variable, debuginfo.DebugVariable) assert isinstance(variable.address, debuginfo.DebugAddress) vaddress = obj.get_symbol_id_value(variable.address.symbol_id) var_ctyp = ctypes.POINTER(get_ctypes_type(variable.typ)) vpointer = ctypes.cast(vaddress, var_ctyp) # Set the attribute: setattr(self, variable_name, vpointer) # Store object for later usage: self._obj = obj def get_symbol_offset(self, name): """ Get the memory address of a symbol """ return self._obj.get_symbol(name).value def load_code_as_module(source_file, reporter=None): """ Load c3 code as a module """ from ..api import c3c # Compile a simple function march = get_current_arch() if march is None: raise NotImplementedError(sys.platform) obj = c3c( [source_file], [], march, debug=True, opt_level=2, reporter=reporter ) # Convert obj to executable module m = Mod(obj) return m def load_obj(obj, imports=None): """ Load an object into memory. Args: obj: the code object to load. imports: A dictionary of functions to attach. Optionally a dictionary of functions that must be imported can be provided. """ return Mod(obj, imports=imports)