987 lines
35 KiB
Python
987 lines
35 KiB
Python
# Copyright (c) 2013, Felipe Andres Manzano
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright notice,
|
|
# this list of conditions and the following disclaimer.
|
|
# * 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.
|
|
# * Neither the name of the copyright holder 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.
|
|
|
|
from abc import ABCMeta, abstractmethod, abstractproperty
|
|
from weakref import WeakValueDictionary
|
|
from cStringIO import StringIO
|
|
from smtlib import *
|
|
import logging
|
|
from mappings import _mmap, _munmap
|
|
|
|
logger = logging.getLogger('MEMORY')
|
|
|
|
class MemoryException(Exception):
|
|
'''
|
|
Memory exceptions
|
|
'''
|
|
def __init__(self, cause, address):
|
|
'''
|
|
Builds a memory exception.
|
|
@param cause: exception message.
|
|
@param address: memory address where the exception occurred.
|
|
'''
|
|
super(MemoryException, self, ).__init__('{} <{}>'.format(cause, address))
|
|
self.cause = cause
|
|
self.address = address
|
|
|
|
def __str__(self):
|
|
return '%s <%s>'%(self.cause, '%08x'%self.address)
|
|
|
|
class SymbolicMemoryException(MemoryException):
|
|
|
|
def __init__(self, cause, address, size, constraint):
|
|
super(SymbolicMemoryException, self, ).__init__(cause, address)
|
|
#the crashing contraint you need to assert
|
|
self.constraint = constraint
|
|
self.size = size
|
|
|
|
def __str__(self):
|
|
return '%s <%s>'%(self.cause, isinstance(self.address, Expression) and repr(self.address) or '%08x'%self.address)
|
|
|
|
class Map(object):
|
|
'''
|
|
A memory map.
|
|
|
|
It represents a convex chunk of memory with a start and an end address.
|
|
It may be implemented as an actual file mapping or as a StringIO/bytearray.
|
|
|
|
>>> ######################################
|
|
^ ^
|
|
start end
|
|
|
|
'''
|
|
__metaclass__ = ABCMeta
|
|
def __init__(self, start, size, perms, name=None):
|
|
''' Abstract memory map.
|
|
@param start: the first valid address.
|
|
@param size: the size of the map.
|
|
@param perms: the access permissions of the map (rwx).
|
|
'''
|
|
assert isinstance(start, (int, long)) and start >= 0, 'Invalid start address'
|
|
assert isinstance(size, (int, long)) and size > 0, 'Invalid end address'
|
|
|
|
super(Map, self).__init__()
|
|
self._start = start
|
|
self._end = start + size
|
|
self._set_perms(perms)
|
|
self._name = name
|
|
|
|
def _get_perms(self):
|
|
''' Gets the access permissions of the map.
|
|
'''
|
|
return self._perms
|
|
|
|
def _set_perms(self, perms):
|
|
''' Sets the access permissions of the map.
|
|
|
|
@param perms: the new permissions.
|
|
'''
|
|
assert isinstance(perms, str) and len(perms) <= 3 and perms.strip() in ['', 'r', 'w', 'x', 'rw', 'r x', 'rx', 'rwx', 'wx', ]
|
|
self._perms = perms
|
|
|
|
#Property
|
|
perms = property(_get_perms, _set_perms)
|
|
|
|
def access_ok(self, access):
|
|
''' Check if there is onough permissions for access '''
|
|
for c in access:
|
|
if c not in self.perms:
|
|
return False
|
|
return True
|
|
|
|
@property
|
|
def start(self):
|
|
return self._start
|
|
|
|
@property
|
|
def end(self):
|
|
return self._end
|
|
|
|
@property
|
|
def name(self):
|
|
return self._name
|
|
|
|
def __len__(self):
|
|
'''Returns the current size in bytes.
|
|
'''
|
|
return self._end - self._start
|
|
|
|
def __repr__(self):
|
|
'''Returns the string representation of the map mapping.
|
|
@rtype: str
|
|
'''
|
|
return '<%s 0x%016x-0x%016x %s>'%(self.__class__.__name__, self.start, self.end, self.perms)
|
|
|
|
def _in_range(self, index):
|
|
''' Returns True if index is in range '''
|
|
if isinstance(index, slice):
|
|
in_range = index.start < index.stop and \
|
|
index.start >= self.start and \
|
|
index.stop <= self.end
|
|
else:
|
|
in_range = index >= self.start and \
|
|
index <= self.end
|
|
return in_range
|
|
|
|
def _get_offset(self, index):
|
|
''' Translates the index to the internal offsets.
|
|
self.start -> 0
|
|
self.start+1 -> 1
|
|
...
|
|
self.end -> len(self)
|
|
'''
|
|
if not self._in_range(index):
|
|
raise IndexError('Map index out of range')
|
|
if isinstance(index, slice):
|
|
index = slice(index.start-self.start, index.stop-self.start)
|
|
else:
|
|
index -= self.start
|
|
return index
|
|
|
|
@abstractmethod
|
|
def __getitem__(self, index):
|
|
'''
|
|
Reads a byte from an address or a sequence of bytes from a range of addresses
|
|
@rtype: byte or array
|
|
|
|
@param index: the address or slice where to obtain the bytes from.
|
|
@return: the character or sequence at the specified address.
|
|
'''
|
|
pass
|
|
|
|
@abstractmethod
|
|
def __setitem__(self, index, value):
|
|
'''
|
|
Writes a byte to an address or a sequence of bytes to a range of addresses
|
|
@param index: the address or slice where to put the data.
|
|
@param value: byte or sequence of bytes to put in this map.
|
|
'''
|
|
pass
|
|
|
|
|
|
@abstractmethod
|
|
def split(self, address):
|
|
pass
|
|
|
|
|
|
class AnonMap(Map):
|
|
''' A concrete anonymous memory map '''
|
|
def __init__(self, start, size, perms, data_init=None, **kwargs):
|
|
''' Builds a concrete anonymous memory map.
|
|
@param start: the first valid address of the map.
|
|
@param size: the size of the map.
|
|
@param perms: the access permissions of the map.
|
|
@param data_init: the data to initialize the map.
|
|
'''
|
|
super(AnonMap, self).__init__(start, size, perms, **kwargs)
|
|
self._data = bytearray(size)
|
|
if not data_init is None:
|
|
assert len(data_init) <= size, 'More initial data than reserved memory'
|
|
self._data[0:len(data_init)] = data_init
|
|
|
|
def __reduce__(self):
|
|
return (self.__class__, (self.start, len(self), self.perms, self._data, ))
|
|
|
|
def split(self, address):
|
|
if address <= self.start:
|
|
return None, self
|
|
if address >= self.end:
|
|
return self, None
|
|
|
|
assert address > self.start and address < self.end
|
|
head = AnonMap(self.start, address-self.start, self.perms, self[self.start:address])
|
|
tail = AnonMap(address, self.end-address, self.perms, self[address:self.end])
|
|
return head,tail
|
|
|
|
def __setitem__(self, index, value):
|
|
assert not isinstance(index, slice) or \
|
|
len(value) == index.stop-index.start
|
|
index = self._get_offset(index)
|
|
self._data[index] = value
|
|
|
|
def __getitem__(self, index):
|
|
index = self._get_offset(index)
|
|
if isinstance(index, slice):
|
|
return map(chr, self._data[index])
|
|
return chr(self._data[index])
|
|
|
|
|
|
class FileMap(Map):
|
|
'''
|
|
A file map.
|
|
|
|
A file is mapped in multiples of the page size. For a file that is not a multiple of the page size,
|
|
the remaining memory is zeroed when mapped, and writes to that region are not written out to the file.
|
|
The effect of changing the size of the underlying file of a mapping on the pages that correspond to
|
|
added or removed regions of the file is unspecified.
|
|
'''
|
|
|
|
def __init__(self, addr, size, perms, filename, offset=0, overlay=None, **kwargs):
|
|
''' Builds a map of memory initialized with the content of filename.
|
|
@param addr: the first valid address of the file map.
|
|
@param size: the size of the file map.
|
|
@param perms: the access permissions of the file map.
|
|
@param filename: the file to map in memory.
|
|
@param offset: the offset into the file where to start the mapping. This offset must be a multiple of pagebitsize.
|
|
'''
|
|
super(FileMap, self).__init__(addr, size, perms, **kwargs)
|
|
assert isinstance(offset, (int, long))
|
|
assert offset >= 0
|
|
self._filename = filename
|
|
self._offset = offset
|
|
with open(filename, 'r') as fileobject:
|
|
fileobject.seek(0, 2)
|
|
file_size = fileobject.tell()
|
|
self._mapped_size = min(size, file_size - offset)
|
|
self._data = _mmap(fileobject.fileno(), offset, self._mapped_size)
|
|
if overlay is not None:
|
|
self._overlay = dict(overlay)
|
|
else:
|
|
self._overlay = dict()
|
|
|
|
def __reduce__(self):
|
|
return (self.__class__, (self.start, len(self), self.perms, self._filename, self._offset, self._overlay))
|
|
|
|
def __del__(self):
|
|
_munmap(self._data, self._mapped_size)
|
|
|
|
def __repr__(self):
|
|
return '<%s [%s+%x] 0x%016x-0x%016x %s>'%(self.__class__.__name__, self._filename, self._offset, self.start, self.end, self.perms)
|
|
|
|
def __setitem__(self, index, value):
|
|
assert not isinstance(index, slice) or \
|
|
len(value) == index.stop-index.start
|
|
index = self._get_offset(index)
|
|
if isinstance(index, slice):
|
|
for i in xrange(index.stop-index.start):
|
|
self._overlay[index.start+i] = value[i]
|
|
else:
|
|
self._overlay[index] = value
|
|
|
|
def __getitem__(self, index):
|
|
def get_byte_at_offset(offset):
|
|
if offset in self._overlay:
|
|
return self._overlay[offset]
|
|
else:
|
|
if offset >= self._mapped_size:
|
|
return '\x00' # , 'Extra data must initially be zero'
|
|
return self._data[offset]
|
|
|
|
index = self._get_offset(index)
|
|
if isinstance(index, slice):
|
|
result = []
|
|
for i in xrange(index.stop-index.start):
|
|
result.append(get_byte_at_offset(i+index.start))
|
|
return result
|
|
else:
|
|
return get_byte_at_offset(index)
|
|
|
|
def split(self, address):
|
|
if address <= self.start:
|
|
return None, self
|
|
if address >= self.end:
|
|
return self, None
|
|
|
|
assert address > self.start and address <= self.end
|
|
head = COWMap(self, size=address-self.start)
|
|
tail = COWMap(self, offset=address-self.start)
|
|
return head, tail
|
|
|
|
|
|
class COWMap(Map):
|
|
'''
|
|
Copy on write based map.
|
|
'''
|
|
def __init__(self, parent, offset=0, perms=None, size=None, **kwargs):
|
|
''' A copy on write copy of parent. Writes to the parent after a copy on write are unspecified.
|
|
@param start the new map address
|
|
@size the new map size
|
|
@param parent: the parent map.
|
|
@param offset: an offset within the parent map from where to create the new map.
|
|
@param size: the size of the new map or max.
|
|
'''
|
|
assert isinstance(parent, Map)
|
|
assert offset >= 0 and offset < len(parent)
|
|
if size is None:
|
|
size=len(parent)-offset
|
|
assert parent.start+offset+size <= parent.end
|
|
if perms is None:
|
|
perms=parent.perms
|
|
|
|
super(COWMap, self).__init__(parent.start+offset, size, perms, **kwargs)
|
|
self._parent = parent
|
|
self._parent.__setitem__ = False
|
|
self._cow = {}
|
|
|
|
def __setitem__(self, index, value):
|
|
assert self._in_range(index)
|
|
assert self.access_ok('w')
|
|
if isinstance(index, slice):
|
|
for i in xrange(index.stop-index.start):
|
|
self._cow[index.start+i] = value[i]
|
|
else:
|
|
self._cow[index] = value
|
|
|
|
def __getitem__(self, index):
|
|
assert self._in_range(index)
|
|
assert self.access_ok('r')
|
|
|
|
if isinstance(index, slice):
|
|
result = []
|
|
for i in xrange(index.start, index.stop):
|
|
if i in self._cow:
|
|
result.append(self._cow[i])
|
|
else:
|
|
result.append(self._parent[i])
|
|
return result
|
|
else:
|
|
if index in self._cow:
|
|
return self._cow[index]
|
|
else:
|
|
return self._parent[index]
|
|
|
|
def split(self, address):
|
|
if address <= self.start:
|
|
return None, self
|
|
if address >= self.end:
|
|
return self, None
|
|
|
|
assert address > self.start and address < self.end
|
|
head = COWMap(self, size=address-self.start)
|
|
tail = COWMap(self, offset=address-self.start)
|
|
return head, tail
|
|
|
|
class Memory(object):
|
|
__metaclass__ = ABCMeta
|
|
|
|
'''
|
|
The memory manager.
|
|
This class handles all virtual memory mappings and symbolic chunks.
|
|
'''
|
|
def __init__(self, maps=None):
|
|
'''
|
|
Builds a memory manager.
|
|
'''
|
|
super(Memory, self).__init__()
|
|
if maps is None:
|
|
self._maps = set()
|
|
else:
|
|
self._maps = set(maps)
|
|
self._page2map = WeakValueDictionary() #{page -> ref{MAP}}
|
|
self._callbacks = {}
|
|
for m in self._maps:
|
|
for i in range(self._page(m.start), self._page(m.end)):
|
|
assert i not in self._page2map
|
|
self._page2map[i] = m
|
|
|
|
def __reduce__(self):
|
|
return (self.__class__, (self._maps, ), {'_callbacks': self._callbacks})
|
|
|
|
@abstractproperty
|
|
def memory_bit_size(self):
|
|
return 32
|
|
|
|
@abstractproperty
|
|
def page_bit_size(self):
|
|
return 12
|
|
|
|
@property
|
|
def memory_size(self):
|
|
return 1 << self.memory_bit_size
|
|
|
|
@property
|
|
def page_size(self):
|
|
return 1 << self.page_bit_size
|
|
|
|
@property
|
|
def memory_mask(self):
|
|
return self.memory_size - 1
|
|
|
|
@property
|
|
def page_mask(self):
|
|
return self.page_size - 1
|
|
|
|
@property
|
|
def maps(self):
|
|
return self._maps
|
|
|
|
def _ceil(self, address):
|
|
'''
|
|
Returns the smallest page boundary value not less than the address.
|
|
@rtype: int
|
|
@param address: the address to calculate its ceil.
|
|
@return: the ceil of C{address}.
|
|
'''
|
|
return ((address | self.page_mask) + 1 ) & self.memory_mask
|
|
|
|
def _floor(self, address):
|
|
'''
|
|
Returns largest page boundary value not greater than the address.
|
|
@rtype: int
|
|
|
|
@param address: the address to calculate its floor.
|
|
@return: the floor of C{address}.
|
|
'''
|
|
return address & ~self.page_mask
|
|
|
|
def _page(self, address):
|
|
'''
|
|
Calculates the page number of an address.
|
|
@rtype: int
|
|
|
|
@param address: the address to calculate its page number.
|
|
@return: the page number address of C{address}.
|
|
'''
|
|
return address >> self.page_bit_size
|
|
|
|
def _search(self, size, start=None, counter=0):
|
|
'''
|
|
Recursively searches the address space for enough free space to allocate C{size} bytes.
|
|
@rtype: int
|
|
|
|
@param size: the size in bytes to allocate.
|
|
@param start: an address from where to start the search.
|
|
@param counter: internal parameter to know if all the memory was already scanned.
|
|
@return: the address of an available space to map C{size} bytes.
|
|
@raise MemoryException: if there is no space available to allocate the desired memory.
|
|
|
|
|
|
@todo: Document what happens when you try to allocate something that goes round the address 32/64 bit representation.
|
|
'''
|
|
assert size & self.page_mask == 0
|
|
if start is None:
|
|
end = {32:0xf8000000, 64: 0x0000800000000000}[self.memory_bit_size]
|
|
start = end-size
|
|
else:
|
|
if start > self.memory_size - size:
|
|
start = self.memory_size - size
|
|
end = start+size
|
|
|
|
|
|
consecutive_free = 0
|
|
for p in xrange(self._page(end-1),-1,-1):
|
|
if p not in self._page2map:
|
|
consecutive_free += 0x1000
|
|
else:
|
|
consecutive_free = 0
|
|
if consecutive_free >= size:
|
|
return p << self.page_bit_size
|
|
counter+=1
|
|
if counter >= self.memory_size/self.page_size:
|
|
raise MemoryException('Not enough memory', 0)
|
|
|
|
return self._search( size, self.memory_size-size, counter )
|
|
|
|
def mmapFile(self, addr, size, perms, filename, offset=0):
|
|
'''
|
|
Creates a new file mapping in the memory address space.
|
|
|
|
@rtype: int
|
|
|
|
@param addr: the starting address (took as hint). If C{addr} is C{0} the first big enough
|
|
chunk of memory will be selected as starting address.
|
|
@param size: the contents of a file mapping are initialized using C{size} bytes starting
|
|
at offset C{offset} in the file C{filename}.
|
|
@param perms: the access permissions to this memory.
|
|
@param filename: the pathname to the file to map.
|
|
@param offset: the contents of a file mapping are initialized using C{size} bytes starting
|
|
at offset C{offset} in the file C{filename}.
|
|
@return: the starting address where the file was mapped.
|
|
@raise error:
|
|
- 'Address shall be concrete' if C{addr} is not an integer number.
|
|
- 'Address too big' if C{addr} goes beyond the limit of the memory.
|
|
- 'Map already used' if the piece of memory starting in C{addr} and with length C{size} isn't free.
|
|
'''
|
|
#If addr is NULL, the system determines where to allocate the region.
|
|
assert addr is None or isinstance(addr, (int, long)), 'Address shall be concrete'
|
|
assert addr < self.memory_size, 'Address too big'
|
|
assert size > 0
|
|
|
|
#address is rounded down to the nearest multiple of the allocation granularity
|
|
if addr is not None:
|
|
addr = self._floor(addr)
|
|
|
|
#size value is rounded up to the next page boundary
|
|
size = self._ceil(size-1)
|
|
|
|
#If zero search for a spot
|
|
addr = self._search(size, addr)
|
|
|
|
#It should not be allocated
|
|
for i in xrange(self._page(addr), self._page(addr+size)):
|
|
assert not i in self._page2map, 'Map already used'
|
|
|
|
#Create the map
|
|
m = FileMap(addr, size, perms, filename, offset)
|
|
|
|
#Okay, ready to alloc
|
|
self._add(m)
|
|
|
|
logger.debug('New file-memory map @%x size:%x', addr, size)
|
|
return addr
|
|
|
|
def mmap(self, addr, size, perms, data_init=None, name=None):
|
|
'''
|
|
Creates a new mapping in the memory address space.
|
|
|
|
@rtype: int
|
|
|
|
@param addr: the starting address (took as hint). If C{addr} is C{0} the first big enough
|
|
chunk of memory will be selected as starting address.
|
|
@param size: the length of the mapping.
|
|
@param perms: the access permissions to this memory.
|
|
@param data_init: optional data to initialize this memory.
|
|
@param name: optional name to give to this mapping
|
|
@return: the starting address where the memory was mapped.
|
|
@raise error:
|
|
- 'Address shall be concrete' if C{addr} is not an integer number.
|
|
- 'Address too big' if C{addr} goes beyond the limit of the memory.
|
|
- 'Map already used' if the piece of memory starting in C{addr} and with length C{size} isn't free.
|
|
'''
|
|
#If addr is NULL, the system determines where to allocate the region.
|
|
assert addr is None or type(addr) in [int, long], 'Address shall be concrete'
|
|
assert addr < self.memory_size, 'Address too big'
|
|
|
|
|
|
#address is rounded down to the nearest multiple of the allocation granularity
|
|
if addr is not None:
|
|
addr = self._floor(addr)
|
|
|
|
#size value is rounded up to the next page boundary
|
|
size = self._ceil(size-1)
|
|
|
|
#If zero search for a spot
|
|
addr = self._search(size, addr)
|
|
|
|
#It should not be allocated
|
|
for i in xrange(self._page(addr), self._page(addr+size)):
|
|
assert not i in self._page2map, 'Map already used'
|
|
|
|
#Create the anonymous map
|
|
m = AnonMap(start=addr, size=size, perms=perms, data_init=data_init )
|
|
|
|
#Okay, ready to alloc
|
|
self._add(m)
|
|
|
|
logger.debug('New memory map @%x size:%x', addr, size)
|
|
return addr
|
|
|
|
def _add(self, m):
|
|
assert isinstance(m, Map)
|
|
assert m not in self._maps
|
|
assert m.start & self.page_mask ==0
|
|
assert m.end & self.page_mask ==0
|
|
self._maps.add(m)
|
|
#updating the page to map translation
|
|
for i in range(self._page(m.start), self._page(m.end)):
|
|
self._page2map[i] = m
|
|
|
|
def _del(self, m):
|
|
assert isinstance(m, Map)
|
|
assert m in self._maps
|
|
#remove m pages from the page2maps..
|
|
for p in xrange(self._page(m.start), self._page(m.end)):
|
|
del self._page2map[p]
|
|
#remove m from the maps set
|
|
self._maps.remove(m)
|
|
|
|
def _get(self, address):
|
|
'''
|
|
Returns the L{MMap} object containing the address.
|
|
@rtype: L{MMap}
|
|
|
|
@param address: the address to obtain its mapping.
|
|
|
|
@todo: symbolic address
|
|
'''
|
|
return self._page2map[self._page(address)]
|
|
|
|
|
|
def mappings(self):
|
|
'''
|
|
Returns a sorted list of all the mappings for this memory.
|
|
|
|
@rtype: list
|
|
|
|
@return: a list of mappings.
|
|
'''
|
|
result = []
|
|
for m in self.maps:
|
|
if isinstance(m, AnonMap):
|
|
result.append((m.start, m.end, m.perms, 0, ''))
|
|
elif isinstance(m, FileMap):
|
|
result.append((m.start, m.end, m.perms, m._offset, m._filename))
|
|
else:
|
|
result.append((m.start, m.end, m.perms, 0, m.name))
|
|
|
|
return sorted(result)
|
|
|
|
def __str__(self):
|
|
return '\n'.join(['%016x-%016x % 4s %08x %s'%(start, end, p, offset, name or '') for start, end, p, offset, name in self.mappings()])
|
|
|
|
def _maps_in_range(self, start, end):
|
|
'''
|
|
Generates the list of maps that overlaps with the range [start:end]
|
|
'''
|
|
|
|
# Search for the first matching map
|
|
addr = start
|
|
while addr < end :
|
|
if addr not in self:
|
|
addr += self.page_size
|
|
else:
|
|
m = self._page2map[self._page(addr)]
|
|
yield m
|
|
addr = m.end
|
|
|
|
|
|
def munmap(self, start, size):
|
|
'''
|
|
Deletes the mappings for the specified address range and causes further references to addresses
|
|
within the range to generate invalid memory references.
|
|
@param start: the starting address to delete.
|
|
@param size: the length of the unmapping.
|
|
'''
|
|
start = self._floor(start)
|
|
end = self._ceil(start+size-1)
|
|
|
|
for m in self._maps_in_range(start, end):
|
|
self._del(m)
|
|
head, tail = m.split(start)
|
|
middle, tail = tail.split(end)
|
|
assert middle is not None
|
|
if head:
|
|
self._add(head)
|
|
if tail:
|
|
self._add(tail)
|
|
|
|
|
|
logger.debug('Unmap memory @%x size:%x', start, size)
|
|
|
|
def mprotect(self, start, size, perms):
|
|
assert size > 0
|
|
start = self._floor(start)
|
|
end = self._ceil(start+size-1)
|
|
|
|
for m in self._maps_in_range(start, end):
|
|
self._del(m)
|
|
|
|
head, tail = m.split(start)
|
|
middle, tail = tail.split(end)
|
|
|
|
assert middle is not None
|
|
middle.perms=perms
|
|
self._add(middle)
|
|
|
|
if head:
|
|
self._add(head)
|
|
if tail:
|
|
self._add(tail)
|
|
|
|
|
|
#Permissions
|
|
def __contains__(self, address):
|
|
return self._page(address) in self._page2map
|
|
|
|
def set_callback(self, name, callback):
|
|
'''
|
|
Set or remove a callback for a named event.
|
|
Takes a callable that potentially receives parameter.
|
|
'''
|
|
if callback is None:
|
|
self._callbacks.pop(name,None)
|
|
return
|
|
if not hasattr(callback, '__call__'):
|
|
raise ValueError('Callback must be callable')
|
|
self.callback[name] = callback
|
|
|
|
def perms(self, index):
|
|
# not happy with ths interface.
|
|
if isinstance(index, slice):
|
|
# get the more restrictive set of perms for the range
|
|
raise NotImplementedError('No perms for slices')
|
|
else:
|
|
return self._get(index).perms
|
|
|
|
def access_ok(self, index, access):
|
|
if isinstance(index, slice):
|
|
assert index.stop - index.start > 0
|
|
result = []
|
|
addr = index.start
|
|
while addr < index.stop:
|
|
if addr not in self:
|
|
return False
|
|
m = self._get(addr)
|
|
size = min(m.end-addr, index.stop-addr)
|
|
|
|
if not m.access_ok(access):
|
|
return False
|
|
addr+=size
|
|
assert addr == index.stop
|
|
return True
|
|
else:
|
|
if index not in self:
|
|
return False
|
|
m = self._get(index)
|
|
return m.access_ok(access)
|
|
|
|
#write and read potentially symbolic bytes at symbolic indexes
|
|
def read(self, addr, size):
|
|
if not self.access_ok(slice(addr, addr+size), 'r'):
|
|
raise MemoryException('No access reading', addr)
|
|
|
|
assert size > 0
|
|
result = []
|
|
start = addr
|
|
stop = addr+size
|
|
p = addr
|
|
while p < stop:
|
|
m = self._get(p)
|
|
|
|
_size = min(m.end-p, stop-p)
|
|
result += m[p:p+_size]
|
|
p+=_size
|
|
assert p == stop
|
|
|
|
if 'read' in self._callbacks:
|
|
self._callbacks['read'](addr, result)
|
|
|
|
return result
|
|
|
|
def write(self, addr, buf):
|
|
size = len(buf)
|
|
if not self.access_ok(slice(addr, addr + size), 'w'):
|
|
raise MemoryException('No access writing', addr)
|
|
assert size > 0
|
|
stop = addr + size
|
|
start = addr
|
|
while addr < stop:
|
|
m = self._get(addr)
|
|
size = min(m.end-addr, stop-addr)
|
|
m[addr:addr+size] = buf[addr-start:addr-start+size]
|
|
addr+=size
|
|
assert addr == stop
|
|
|
|
if 'write' in self._callbacks:
|
|
self._callbacks['write'](addr, buf)
|
|
|
|
def _get_size(self, size):
|
|
return size
|
|
|
|
def __setitem__(self, index, value):
|
|
if isinstance(index, slice):
|
|
size = self._get_size(index.stop-index.start)
|
|
assert len(value) == size #raise proper Error?
|
|
self.write(index.start, value)
|
|
else:
|
|
self.write(index, (value,))
|
|
|
|
def __getitem__(self, index):
|
|
if isinstance(index, slice):
|
|
result = self.read(index.start, index.stop - index.start)
|
|
else:
|
|
result = self.read(index, 1)[0]
|
|
return result
|
|
|
|
|
|
|
|
class SMemory(Memory):
|
|
'''
|
|
The symbolic memory manager.
|
|
This class handles all virtual memory mappings and symbolic chunks.
|
|
@todo: improve comments
|
|
'''
|
|
def __init__(self, constraints, symbols=None, *args, **kwargs):
|
|
''' Builds a map of memory.
|
|
@param constraints: a set of constraints
|
|
@param addressbitsize: the size in bits of the address space (default=32).
|
|
@param pagebitsize: the size in bits of a page boundary memory (default=12).
|
|
'''
|
|
super(SMemory, self).__init__(*args, **kwargs)
|
|
assert isinstance(constraints, ConstraintSet)
|
|
self._constraints = constraints
|
|
if symbols is None:
|
|
self._symbols = {}
|
|
else:
|
|
self._symbols = dict(symbols)
|
|
|
|
def __reduce__(self):
|
|
return (self.__class__, (self.constraints, self._symbols, self._maps, ) )
|
|
|
|
@property
|
|
def constraints(self):
|
|
return self._constraints
|
|
|
|
def _get_size(self, size):
|
|
if isinstance(size, BitVec):
|
|
size = arithmetic_simplifier(size)
|
|
else:
|
|
size = BitVecConstant(self.memory_bit_size, size)
|
|
assert isinstance(size, BitVecConstant)
|
|
return size.value
|
|
|
|
|
|
def munmap(self, start, size):
|
|
'''
|
|
Deletes the mappings for the specified address range and causes further references to addresses
|
|
within the range to generate invalid memory references.
|
|
@param start: the starting address to delete.
|
|
@param size: the length of the unmapping.
|
|
'''
|
|
for addr in xrange(start,start+size):
|
|
if addr in self._symbols:
|
|
del self._symbols[addr]
|
|
super(SMemory, self).munmap(start,size)
|
|
|
|
def read(self, address, size):
|
|
''' Read a stream of potentially symbolic bytes from a potentially symbolic address '''
|
|
size = self._get_size(size)
|
|
assert not isinstance(size, Expression)
|
|
|
|
|
|
if isinstance(address, Expression):
|
|
assert solver.check(self.constraints)
|
|
logger.info('Reading %d bytes from symbolic address %s', size, address)
|
|
try:
|
|
solutions = solver.get_all_values(self.constraints, address, maxcnt=0x1000) #if more than 0x3000 exception
|
|
except TooManySolutions, e:
|
|
m, M = solver.minmax(self.constraints, address)
|
|
logger.info('Got TooManySolutions on a symbolic read. Range [%x, %x]. Not crashing!', m, M)
|
|
logger.info('Memory:%s', self)
|
|
|
|
crashing_condition = True
|
|
for start, end, perms, offset, name in self.mappings():
|
|
if start <= M+size and end >= m :
|
|
if 'r' in perms:
|
|
crashing_condition = Operators.AND(Operators.OR( (address+size).ult(start), address.uge(end) ), crashing_condition)
|
|
|
|
if solver.can_be_true(self.constraints, crashing_condition):
|
|
raise SymbolicMemoryException('No access reading symbolic', address, size, crashing_condition)
|
|
|
|
|
|
#INCOMPLETE Result! We could also fork once for every map
|
|
logger.info('INCOMPLETE Result! Using the sampled solutions we have as result')
|
|
condition = False
|
|
for base in e.solutions:
|
|
condition = Operators.OR(address == base, condition )
|
|
raise ForkState(condition)
|
|
|
|
#So here we have all potential solutions to addreess
|
|
assert len(solutions) > 0
|
|
|
|
|
|
crashing_condition = False
|
|
for base in solutions:
|
|
if any(not self.access_ok(i, 'r') for i in xrange(base, base + size, self.page_size)):
|
|
crashing_condition = Operators.OR(address == base, crashing_condition)
|
|
|
|
if solver.can_be_true(self.constraints, crashing_condition):
|
|
raise SymbolicMemoryException('No access reading symbolic', address, size, crashing_condition)
|
|
|
|
condition = False
|
|
for base in solutions:
|
|
condition = Operators.OR(address == base, condition )
|
|
|
|
result = []
|
|
#consider size ==1 to read following code
|
|
for offset in range(size):
|
|
#Given ALL solutions for the symbolic address
|
|
for base in solutions:
|
|
addr_value = base + offset
|
|
byte = Operators.ORD(self._get(addr_value)[addr_value])
|
|
if addr_value in self._symbols:
|
|
for condition, value in self._symbols[addr_value]:
|
|
byte = Operators.ITEBV(8, condition, Operators.ORD(value), byte)
|
|
if len(result) > offset:
|
|
result[offset] = Operators.ITEBV(8, address == base, byte, result[offset])
|
|
else:
|
|
result.append(byte)
|
|
assert len(result) == offset+1
|
|
return map(Operators.CHR, result)
|
|
else:
|
|
result = map(Operators.ORD, super(SMemory, self).read(address, size))
|
|
for offset in range(size):
|
|
if address+offset in self._symbols:
|
|
for condition, value in self._symbols[address+offset]:
|
|
if condition is True:
|
|
result[offset] = Operators.ORD(value)
|
|
else:
|
|
result[offset] = Operators.ITEBV(8, condition, Operators.ORD(value), result[offset])
|
|
return map(Operators.CHR, result)
|
|
|
|
def write(self, address, value):
|
|
size = len(value)
|
|
if isinstance(address, Expression):
|
|
|
|
solutions = solver.get_all_values(self.constraints, address, maxcnt=0x1000) #if more than 0x3000 exception
|
|
|
|
crashing_condition = False
|
|
for base in solutions:
|
|
if any(not self.access_ok(i, 'w') for i in xrange(base, base + size, self.page_size)):
|
|
crashing_condition = Operators.OR(address == base, crashing_condition)
|
|
|
|
if solver.can_be_true(self.constraints, crashing_condition):
|
|
raise SymbolicMemoryException('No access writing symbolic', address, size, crashing_condition)
|
|
|
|
for offset in xrange(size):
|
|
for base in solutions:
|
|
condition = base == address
|
|
self._symbols.setdefault(base+offset, []).append((condition, value[offset]))
|
|
|
|
else:
|
|
|
|
for offset in xrange(size):
|
|
if isinstance(value[offset], Expression):
|
|
if not self.access_ok(address+offset, 'w'):
|
|
raise MemoryException('No access writing', address+offset)
|
|
self._symbols[address+offset] = [(True, value[offset])]
|
|
else:
|
|
# overwrite all previous items
|
|
if address+offset in self._symbols:
|
|
del self._symbols[address+offset]
|
|
super(SMemory, self).write(address+offset, [value[offset]])
|
|
|
|
|
|
class Memory32(Memory):
|
|
memory_bit_size = 32
|
|
page_bit_size = 12
|
|
|
|
class Memory64(Memory):
|
|
memory_bit_size = 64
|
|
page_bit_size = 12
|
|
|
|
class SMemory32(SMemory):
|
|
memory_bit_size = 32
|
|
page_bit_size = 12
|
|
|
|
class SMemory32L(SMemory):
|
|
memory_bit_size = 32
|
|
page_bit_size = 13
|
|
|
|
class SMemory64(SMemory):
|
|
memory_bit_size = 64
|
|
page_bit_size = 12 |