Пример #1
0
class PositionMapping:

    __slots__ = ('_pos', '_posmap')

    DUPLICATION_CHECK = True

    def __init__(self):
        self._pos = 0
        self._posmap = SortedDict()

    def items(self):
        return self._posmap.items()

    #
    # Properties
    #

    @property
    def pos(self):
        return self._pos

    @pos.setter
    def pos(self, v):
        self._pos = v

    #
    # Public methods
    #

    def add_mapping(self, start_pos, length, obj):
        # duplication check
        if self.DUPLICATION_CHECK:
            try:
                pre = next(self._posmap.irange(maximum=start_pos, reverse=True))
                if start_pos in self._posmap[pre]:
                    raise ValueError("New mapping is overlapping with an existing element.")
            except StopIteration:
                pass

        self._posmap[start_pos] = PositionMappingElement(start_pos, length, obj)

    def tick_pos(self, delta):
        self._pos += delta

    def get_node(self, pos):
        element = self.get_element(pos)
        if element is None:
            return None
        return element.obj

    def get_element(self, pos):
        try:
            pre = next(self._posmap.irange(maximum=pos, reverse=True))
        except StopIteration:
            return None

        element = self._posmap[pre]
        if pos in element:
            return element
        return None
Пример #2
0
class Index:
    def __init__(self, *keys):
        self.keys = keys
        self.keyer = operator.attrgetter(*keys)
        self.list = SortedDict()

    def reindex(self):
        rows = [i for i in self.list.values()]
        self.list = SortedDict()
        for i in rows:
            self.add(i)

    def add(self, object):
        key = self.keyer(object)
        self.list[key] = object

    def remove(self, object):
        key = self.keyer(object)
        del self.list[key]

    def find(self, start, reverse=False):
        if reverse:
            return (self.list[i]
                    for i in self.list.irange(maximum=start, reverse=True))
        return (self.list[i] for i in self.list.irange(start))
Пример #3
0
class PositionMapping:

    __slots__ = ('_pos', '_posmap')

    DUPLICATION_CHECK = True

    def __init__(self):
        self._pos = 0
        self._posmap = SortedDict()

    def items(self):
        return self._posmap.items()

    #
    # Properties
    #

    @property
    def pos(self):
        return self._pos

    @pos.setter
    def pos(self, v):
        self._pos = v

    #
    # Public methods
    #

    def add_mapping(self, start_pos, length, obj):
        # duplication check
        if self.DUPLICATION_CHECK:
            try:
                pre = next(self._posmap.irange(maximum=start_pos, reverse=True))
                if start_pos in self._posmap[pre]:
                    raise ValueError("New mapping is overlapping with an existing element.")
            except StopIteration:
                pass

        self._posmap[start_pos] = PositionMappingElement(start_pos, length, obj)

    def tick_pos(self, delta):
        self._pos += delta

    def get_node(self, pos):
        element = self.get_element(pos)
        if element is None:
            return None
        return element.obj

    def get_element(self, pos):
        try:
            pre = next(self._posmap.irange(maximum=pos, reverse=True))
        except StopIteration:
            return None

        element = self._posmap[pre]
        if pos in element:
            return element
        return None
Пример #4
0
def test_irange():
    mapping = [(val, pos) for pos, val in enumerate(string.ascii_lowercase)]
    temp = SortedDict(7, mapping)
    for start in range(26):
        for stop in range(start + 1, 26):
            result = list(string.ascii_lowercase[start:stop])
            assert list(temp.irange(result[0], result[-1])) == result
def test_irange():
    mapping = [(val, pos) for pos, val in enumerate(string.ascii_lowercase)]
    temp = SortedDict(7, mapping)
    for start in range(26):
        for stop in range(start + 1, 26):
            result = list(string.ascii_lowercase[start:stop])
            assert list(temp.irange(result[0], result[-1])) == result
Пример #6
0
def print_events_between_dates(events: SortedDict, start_date: datetime,
                               end_date: datetime):
    date_keys_between_dates = events.irange(minimum=start_date,
                                            maximum=end_date,
                                            inclusive=(True, True))
    events = [(event, date_key) for date_key in date_keys_between_dates
              for event in events[date_key]]
    for event, date in events:
        print("{event} | {date}".format(event=event, date=date))
Пример #7
0
def simulate(arrival_rate,exercise_duration,exercise_number,alpha,graph,start_node,duration,mean_fitness,std_fitness):
    arrival_duration = 1/arrival_rate
    geom_p = 1/(exercise_number+1)
    queue = 0
    available_benches = set([node for node in graph.nodes if node[0]=='B'])
    occupied_benches = dict([])
    available_weights = SortedDict(zip(map(lambda x:x*5,range(len(graph.nodes))),filter(lambda x:x[0]=='S',graph.nodes)))
    events=[]
    time=np.random.exponential(arrival_duration)
    event='arrival'
    requests=set([])
    schedule_items=0
    cur_node = start_node
    shortest_paths = nx.all_pairs_dijkstra(graph)
    while time < duration:
        event_type=event[0]
        if event_type == 'arrival':
            if len(available_benches) == 0:
                queue+=1
            else:
                fitness=np.random.normal(mean_fitness,std_fitness)
                bench=available_benches.pop()
                occupied_benches[bench]=fitness
                max_weight=np.random.uniform(high=2.0)*fitness
                weight=next(available_weights.irange(max_weight,reverse=true)))
                requests.add(Request(time,'delivery',weight,available_weights.pop(weight),bench))
            next_arrival=np.random.exponential(arrival_duration)
        elif event_type == 'departure':
            if queue > 0 :
                queue-=1
                event=('arrival')
                break
        elif event_type == 'delivery_request':
            bench=event[1]
            fitness=occupied_benches[bench]
            max_weight=np.random.uniform(high=2.0)*fitness
            weight=next(available_weights.irange(max_weight,reverse=true)))
            requests.add(Request(time,'delivery',weight,available_weights.pop(weight),bench))
            if schedule_items==0:
                r.solve_schedule(
        elif event_type == 'delivery_completed':
            
        (time,event)=events.pop()
Пример #8
0
class DependencyViewer(BasePlugin):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.transitions: Set[Tuple[int, int]] = set()
        self.covered_blocks = SortedDict()

        self.sink_color = Qt.yellow

    def color_insn(self, addr, selected) -> Optional[QColor]:
        if not selected:
            try:
                block_addr = next(
                    self.covered_blocks.irange(maximum=addr, reverse=True))
            except StopIteration:
                return None
            block_size = self.covered_blocks[block_addr]
            if block_addr <= addr < block_addr + block_size:
                return QColor(0xa5, 0xd0, 0xf3)
        return None

    FUNC_COLUMNS = ('Vuln Sink', )

    def extract_func_column(self, func, idx):
        assert idx == 0

        func_name = func.demangled_name
        if "<" in func_name or "{" in func_name:
            func_name = normalize_cpp_function_name(func_name)
            if "(" in func_name:
                # only take function name
                func_name = func_name[:func_name.index("(")]

        vulntype_and_sinks = sink_manager.get_function_sinks(func_name)
        if not vulntype_and_sinks:
            return 0, ''

        vuln_type, sink = vulntype_and_sinks[0]
        return 1, VulnerabilityType.to_string(vuln_type)

    def color_func(self, func) -> Optional[QColor]:
        # test if we have a match
        match = sink_manager.has_function_sink(func.demangled_name)
        if match:
            return self.sink_color

        return None
Пример #9
0
class ContactList:
    def __init__(self):
        self.contact_map = SortedDict()
        self.name_map = SortedDict()
        self.phone_map = SortedDict()
        self.email_map = SortedDict()
        self.size = 0
        self.counter = 1

    def add_contact(self, name='', phone_number='', email=''):
        self.contact_map[self.counter] = Contact(name, phone_number, email)
        self.name_map[name] = self.counter
        self.phone_map[phone_number] = self.counter
        self.email_map[email] = self.counter
        self.counter += 1
        self.size += 1

    def get_by_name(self, name):
        n_id = self.name_map[name]
        return self.contact_map[n_id]

    def get_by_phone(self, phone):
        p_id = self.phone_map[phone]
        return self.contact_map[p_id]

    def get_by_email(self, email):
        e_id = self.email_map[email]
        return self.contact_map[e_id]

    def get_by_id(self, c_id):
        return self.contact_map[c_id]

    def remove(self, c_id):
        contact = self.contact_map[c_id]
        del self.name_map[contact.name]
        del self.phone_map[contact.phone_number]
        del self.email_map[contact.email]
        del self.contact_map[c_id]
        self.size -= 1

    def get_contacts_ordered_by_name(self):
        name_list = self.name_map.irange('ga', 'hzzz')
        ordered_name_list = []
        for name in name_list:
            ordered_name_list.append(self.contact_map[self.name_map[name]])
        return ordered_name_list
Пример #10
0
class LogSystem:

    def __init__(self):
        self.tweets = SortedDict()
        self.granularity = {
            "Year": 0,
            "Month": 1,
            "Day": 2,
            "Hour": 3,
            "Minute": 4,
            "Second": 5,
        }

        self.min = ["2000", "01", "01", "00", "00", "00"]
        self.max = ["2017", "12", "31", "23", "59", "59"]


    def put(self, id: int, timestamp: str) -> None:
        # Time Complexity: O(log N)
        self.tweets[timestamp] = id


    def retrieve(self, s: str, e: str, gra: str) -> List[int]:
        # Time Complexity: O(num_answer + log N)

        ret = []
        gran_s = self.granularize(s, gra, True)
        gran_e = self.granularize(e, gra, False)


        for timestamp in self.tweets.irange(gran_s, gran_e):
            ret.append(self.tweets[timestamp])

        return ret


    def granularize(self, timestamp: str, gra: str, start: bool) -> int:
        last_kept = self.granularity[gra]
        components = timestamp.split(":")[:last_kept + 1]

        if start:
            components += self.min[last_kept + 1:]
        else:
            components += self.max[last_kept + 1:]

        return ":".join(components)
Пример #11
0
class QFeatureMap(QWidget):
    """
    Byte-level map of the memory space.
    """
    def __init__(self, disasm_view, parent=None):
        super().__init__(parent)

        self.disasm_view = disasm_view
        self.workspace = disasm_view.workspace
        self.instance = self.workspace.instance

        self.orientation = Orientation.Vertical

        # widgets
        self.view = None  # type: QFeatureMapView

        # items
        self._insn_indicators = []

        # data instance
        self.addr = ObjectContainer(
            None, name='The current address of the Feature Map.')

        # cached values
        self._addr_to_region = SortedDict()
        self._regionaddr_to_offset = SortedDict()
        self._offset_to_regionaddr = SortedDict()
        self._total_size = None
        self._regions_painted = False

        self._init_widgets()
        self._register_events()

    def sizeHint(self):
        return QSize(25, 25)

    #
    # Public methods
    #

    def refresh(self):

        if self.view is None:
            return

        if not self._regions_painted:
            self._regions_painted = True
            self._paint_regions()

    def select_offset(self, offset):

        addr = self._get_addr_from_pos(offset)
        if addr is None:
            return
        self.addr.am_obj = addr
        self.addr.am_event()

    #
    # Private methods
    #

    def _init_widgets(self):
        self.view = QFeatureMapView(self)

        layout = QHBoxLayout()
        layout.addWidget(self.view)

        layout.setContentsMargins(0, 0, 0, 0)

        self.setLayout(layout)

    def _register_events(self):
        self.disasm_view.infodock.selected_insns.am_subscribe(
            self._paint_insn_indicators)

    def _paint_regions(self):

        cfb = self.instance.cfb_container.am_obj

        if cfb is None:
            return

        # colors
        func_color = Conf.feature_map_color_regular_function
        data_color = Conf.feature_map_color_data
        unknown_color = Conf.feature_map_color_unknown
        delimiter_color = Conf.feature_map_color_delimiter
        if self._total_size is None:
            # calculate the total number of bytes
            b = 0
            self._addr_to_region.clear()
            self._regionaddr_to_offset.clear()
            for mr in cfb.regions:
                self._addr_to_region[mr.addr] = mr
                self._regionaddr_to_offset[mr.addr] = b
                self._offset_to_regionaddr[b] = mr.addr
                b += self._adjust_region_size(mr)
            self._total_size = b

        # iterate through all items and draw the image
        offset = 0
        total_width = self.width()
        current_region = None
        height = self.height()
        print(total_width)
        for addr, obj in cfb.ceiling_items():

            # are we in a new region?
            new_region = False
            if current_region is None or not (
                    current_region.addr <= addr <
                    current_region.addr + current_region.size):
                current_region_addr = next(
                    self._addr_to_region.irange(maximum=addr, reverse=True))
                current_region = self._addr_to_region[current_region_addr]
                new_region = True

            # adjust size
            adjusted_region_size = self._adjust_region_size(current_region)
            adjusted_size = min(
                obj.size, current_region.addr + adjusted_region_size - addr)
            if adjusted_size <= 0:
                continue

            pos = offset * total_width // self._total_size
            length = adjusted_size * total_width // self._total_size
            offset += adjusted_size

            # draw a rectangle
            if isinstance(obj, Unknown):
                pen = QPen(data_color)
                brush = QBrush(data_color)
            elif isinstance(obj, Block):
                # TODO: Check if it belongs to a function or not
                pen = QPen(func_color)
                brush = QBrush(func_color)
            else:
                pen = QPen(unknown_color)
                brush = QBrush(unknown_color)
            rect = QRectF(pos, 0, length, height)
            self.view._scene.addRect(rect, pen, brush)

            # if at the beginning of a new region, draw a line
            if new_region:
                pen = QPen(delimiter_color)
                self.view._scene.addLine(pos, 0, pos, height, pen)

    def _adjust_region_size(self, memory_region):

        if isinstance(memory_region.object,
                      (cle.ExternObject, cle.TLSObject, cle.KernelObject)):
            # Draw unnecessary objects smaller
            return 80
        else:
            print(memory_region.size, memory_region.object)
            return memory_region.size

    def _get_pos_from_addr(self, addr):

        # find the region it belongs to
        try:
            mr_base = next(
                self._addr_to_region.irange(maximum=addr, reverse=True))
        except StopIteration:
            return None

        # get the base offset of that region
        base_offset = self._regionaddr_to_offset[mr_base]

        offset = base_offset + addr - mr_base
        return offset * self.width() // self._total_size

    def _get_addr_from_pos(self, pos):

        offset = int(pos * self._total_size // self.width())

        try:
            base_offset = next(
                self._offset_to_regionaddr.irange(maximum=offset,
                                                  reverse=True))
        except StopIteration:
            return None

        region_addr = self._offset_to_regionaddr[base_offset]
        return region_addr + offset - base_offset

    def _paint_insn_indicators(self):

        scene = self.view.scene()  # type: QGraphicsScene
        for item in self._insn_indicators:
            scene.removeItem(item)
        self._insn_indicators.clear()

        for selected_insn_addr in self.disasm_view.infodock.selected_insns:
            pos = self._get_pos_from_addr(selected_insn_addr)
            if pos is None:
                continue

            pos -= 1  # this is the top-left x coordinate of our arrow body (the rectangle)

            pen = QPen(Qt.yellow)
            brush = QBrush(Qt.yellow)
            rect = QRectF(pos, 0, 2, 5)
            # rectangle
            item = scene.addRect(rect, pen, brush)
            self._insn_indicators.append(item)
            # triangle
            triangle = QPolygonF()
            triangle.append(QPointF(pos - 1, 5))
            triangle.append(QPointF(pos + 3, 5))
            triangle.append(QPointF(pos + 1, 7))
            triangle.append(QPointF(pos - 1, 5))
            item = scene.addPolygon(triangle, pen, brush)
            self._insn_indicators.append(item)
Пример #12
0
class State:
    """
    The state.

    :ivar str user:     Name of the user.
    :ivar int version:  Version of the state, starting from 0.
    """
    def __init__(self, user, version=None, client=None):
        # metadata info
        self.user = user  # type: str
        self.version = version if version is not None else 0  # type: int
        self.last_push_func = -1
        self.last_push_time = -1

        # the client
        self.client = client  # type: Optional[Client]

        # dirty bit
        self._dirty = True  # type: bool

        # data
        self.functions = {}  # type: Dict[int,Function]
        self.comments = SortedDict()  # type: Dict[int,str]
        self.stack_variables = defaultdict(
            dict)  # type: Dict[int,Dict[int,StackVariable]]
        self.patches = SortedDict()

    @property
    def dirty(self):
        return self._dirty

    def ensure_dir_exists(self, dir_name):
        if not os.path.exists(dir_name):
            os.mkdir(dir_name)
        if not os.path.isdir(dir_name):
            raise RuntimeError(
                "Cannot create directory %s. Maybe it conflicts with an existing file?"
                % dir_name)

    def dump_metadata(self, index, last_push_time: int, last_push_func: int):
        d = {
            "user": self.user,
            "version": self.version,
            "last_push_func": last_push_func,
            "last_push_time": last_push_time
        }
        add_data(index, 'metadata.toml', toml.dumps(d).encode())

    def dump(self, index: git.IndexFile, last_push_func: int,
             last_push_time: int):
        # dump metadata
        self.dump_metadata(index, last_push_time, last_push_func)

        # dump function
        add_data(index, 'functions.toml',
                 toml.dumps(Function.dump_many(self.functions)).encode())

        # dump comments
        add_data(index, 'comments.toml',
                 toml.dumps(Comment.dump_many(self.comments)).encode())

        # dump patches
        add_data(index, 'patches.toml',
                 toml.dumps(Patch.dump_many(self.patches)).encode())

        # dump stack variables, one file per function
        for func_addr, stack_vars in self.stack_variables.items():
            path = os.path.join('stack_vars', "%08x.toml" % func_addr)
            add_data(index, path,
                     toml.dumps(StackVariable.dump_many(stack_vars)).encode())

    @staticmethod
    def load_metadata(tree):
        return toml.loads(tree['metadata.toml'].data_stream.read().decode())

    @classmethod
    def parse(cls, tree, version=None, client=None):
        s = cls(None, client=client)

        # load metadata
        try:
            metadata = cls.load_metadata(tree)
        except:
            # metadata is not found
            raise MetadataNotFoundError()
        s.user = metadata["user"]

        s.version = version if version is not None else metadata["version"]

        # load function
        try:
            funcs_toml = toml.loads(
                tree['functions.toml'].data_stream.read().decode())
        except:
            pass
        else:
            functions = {}
            for func in Function.load_many(funcs_toml):
                functions[func.addr] = func
            s.functions = functions

        # load comments
        try:
            comments_toml = toml.loads(
                tree['comments.toml'].data_stream.read().decode())
        except:
            pass
        else:
            comments = {}
            for comm in Comment.load_many(comments_toml):
                comments[comm.addr] = comm.comment
            s.comments = SortedDict(comments)

        # load patches
        try:
            patches_toml = toml.loads(
                tree['patches.toml'].data_stream.read().decode())
        except:
            pass
        else:
            patches = {}
            for patch in Patch.load_many(patches_toml):
                patches[patch.offset] = patch
            s.patches = SortedDict(patches)

        # load stack variables
        for func_addr in s.functions:
            try:
                # TODO use unrebased address for more durability
                svs_toml = toml.loads(tree[os.path.join(
                    'stack_vars',
                    '%08x.toml' % func_addr)].data_stream.read().decode())
            except:
                pass
            else:
                svs = list(StackVariable.load_many(svs_toml))
                d = dict((v.stack_offset, v) for v in svs)
                if svs:
                    s.stack_variables[svs[0].func_addr] = d

        # clear the dirty bit
        s._dirty = False

        return s

    def copy_state(self, target_state=None):
        if target_state == None:
            print("Cannot copy an empty state (state == None)")
            return

        self.functions = target_state.functions.copy()
        self.comments = target_state.comments.copy()
        self.stack_variables = target_state.stack_variables.copy()
        self.patches = target_state.patches.copy()

    def save(self):
        if self.client is None:
            raise RuntimeError("save(): State.client is None.")
        self.client.save_state(self)

    #
    # Pushers
    #

    @dirty_checker
    def update_metadata(self, last_func_push: str, last_push_time: int):
        pass

    @dirty_checker
    def set_function(self, func):

        if not isinstance(func, Function):
            raise TypeError("Unsupported type %s. Expecting type %s." %
                            (type(func), Function))

        if func.addr in self.functions and self.functions[func.addr] == func:
            # no update is required
            return False

        self.functions[func.addr] = func
        return True

    @dirty_checker
    def set_comment(self, addr, comment):

        if addr in self.comments and self.comments[addr] == comment:
            # no update is required
            return False

        self.comments[addr] = comment
        return True

    @dirty_checker
    def remove_comment(self, addr):
        if addr in self.comments:
            del self.comments[addr]
            return True
        return False

    @dirty_checker
    def set_patch(self, addr, patch):

        if addr in self.patches and self.patches[addr] == patch:
            # no update is required
            return False

        self.patches[addr] = patch
        return True

    @dirty_checker
    def set_stack_variable(self, func_addr, offset, variable):
        if func_addr in self.stack_variables \
                and offset in self.stack_variables[func_addr] \
                and self.stack_variables[func_addr][offset] == variable:
            # no update is required
            return False

        self.stack_variables[func_addr][offset] = variable
        return True

    #
    # Pullers
    #

    def get_function(self, addr):

        if addr not in self.functions:
            raise KeyError("Function %x is not found in the db." % addr)

        return self.functions[addr]

    def get_comment(self, addr):

        if addr not in self.comments:
            raise KeyError("There is no comment at address %#x." % addr)

        cmt = self.comments[addr]
        if is_py2() and isinstance(cmt, unicode):
            cmt = str(cmt)
        return cmt

    def get_comments(self, start_addr, end_addr=None):
        for k in self.comments.irange(start_addr, reverse=False):
            if k >= end_addr:
                break
            cmt = self.comments[k]
            if is_py2() and isinstance(cmt, unicode):
                cmt = str(cmt)
            yield cmt

    def get_patch(self, addr):

        if addr not in self.patches:
            raise KeyError("There is no patch at address %#x." % addr)

        return self.patches[addr]

    def get_patches(self):
        return self.patches.values()

    def get_stack_variable(self, func_addr, offset):
        if func_addr not in self.stack_variables:
            raise KeyError("No stack variables are defined for function %#x." %
                           func_addr)
        if offset not in self.stack_variables[func_addr]:
            raise KeyError(
                "No stack variable exists at offset %d in function %#x." %
                (offset, func_addr))
        return self.stack_variables[func_addr][offset]

    def get_stack_variables(self, func_addr):
        if func_addr not in self.stack_variables:
            raise KeyError("No stack variables are defined for function %#x." %
                           func_addr)
        return self.stack_variables[func_addr].items()

    # TODO: it would be better if we stored the function addr with every state object, like comments
    def get_modified_addrs(self):
        """
        Gets ever address that has been touched in the current state.
        Returns a set of those addresses.

        @rtype: Set(int)
        """
        moded_addrs = set()
        # ==== functions ==== #
        for addr in self.functions:
            moded_addrs.add(addr)

        # ==== comments ==== #
        for addr in self.comments:
            moded_addrs.add(addr)

        # ==== stack vars ==== #
        for addr in self.stack_variables:
            moded_addrs.add(addr)

        return moded_addrs
Пример #13
0
class VectorNav(object):
    TIMEOUT = 0.5  # Serial read timeout, in seconds

    MODE_SYNC = 0
    MODE_ASYNC = 1

    CHECKSUM_8BIT = 0
    CHECKSUM_16BIT = 1

    INITIAL_CRC = 0

    REG_USER_TAG = 0
    REG_MODEL_NUM = 1
    REG_HARDWARE_REV = 2
    REG_SERIAL_NUM = 3
    REG_FIRMWARE_VER = 4
    REG_SERIAL_BAUD = 5
    REG_ASYNC_DOUT_TYPE = 6
    REG_ASYNC_DOUT_FREQ = 7
    REG_PROTO_CTRL = 30
    REG_SYNC_CTRL = 32
    REG_SYNC_STATUS = 33
    REG_BIN_OUT_1 = 75
    REG_BIN_OUT_2 = 76
    REG_BIN_OUT_3 = 77

    FIX_NONE = 0
    FIX_TIME = 1
    FIX_2D = 2
    FIX_3D = 3

    SRC_VECTORNAV = 'VectorNav'

    DEFAULT_BAUD = 115200
    BINARY_OUTPUT_RATE_DIV = 32  # 800Hz/32 = 25Hz

    STATE_TIMEOUT = timedelta(seconds=60)

    DEFAULT_VID = 0x0403
    DEFAULT_PID = 0x6001

    FIELDS = {
        0: {
            0: ('TimeStartup', '<Q'),
            1: ('TimeGps', '<Q'),
            2: ('TimeSyncIn', '<Q'),
            3: ('Ypr', '<3f'),
            4: ('Qtn', '<4f'),
            5: ('AngularRate', '<3f'),
            6: ('PosLla', '<3d'),
            7: ('VelNed', '<3f'),
            8: ('Accel', '<3f'),
            9: ('Imu', '<3f3f'),
            10: ('MagPres', '<5f'),
            11: ('DeltaThetaVel', '<7f'),
            12: ('InsStatus', '<H'),
            13: ('SyncInCnt', '<L'),
            14: ('TimeGpsPps', '<Q')
        },
        1: {
            0: ('TimeStartup', '<Q'),
            1: ('TimeGps', '<Q'),
            2: ('GpsTow', '<Q'),
            3: ('GpsWeek', '<H'),
            4: ('TimeSyncIn', '<Q'),
            5: ('TimeGpsPps', '<Q'),
            6: ('TimeUTC', '<bBBBBBH'),
            7: ('SyncInCnt', '<L')
        },
        2: {
            0: ('ImuStatus', '<H'),
            1: ('UncompMag', '<3f'),
            2: ('UncompAccel', '<3f'),
            3: ('UncompGyro', '<3f'),
            4: ('Temp', '<f'),
            5: ('Pres', '<f'),
            6: ('DeltaTheta', '<4f'),
            7: ('DeltaV', '<3f'),
            8: ('Mag', '<3f'),
            9: ('Accel', '<3f'),
            10: ('AngularRate', '<3f'),
            11: ('SensSat', '<H')
        },
        3: {
            0: ('TimeUTC', '<bBBBBBH'),
            1: ('GpsTow', '<Q'),
            2: ('GpsWeek', '<H'),
            3: ('NumSats', '<B'),
            4: ('Fix', '<B'),
            5: ('GpsPosLla', '<3d'),
            6: ('GpsPosEcef', '<3d'),
            7: ('GpsVelNed', '<3f'),
            8: ('GpsVelEcef', '<3f'),
            9: ('GpsPosU', '<3f'),
            10: ('GpsVelU', '<f'),
            11: ('TimeU', '<L')
        },
        4: {
            0: ('VpeStatus', '<H'),
            1: ('Ypr', '<3f'),
            2: ('Qtn', '<4f'),
            3: ('DCM', '<9f'),
            4: ('MagNed', '<3f'),
            5: ('AccelNed', '<3f'),
            6: ('LinearAccelBody', '<3f'),
            7: ('LinearAccelNed', '<3f'),
            8: ('YprU', '<3f')
        },
        5: {
            0: ('InsStatus', '<H'),
            1: ('PosLla', '<3d'),
            2: ('PosEcef', '<3d'),
            3: ('VelBody', '<3f'),
            4: ('VelNed', '<3f'),
            5: ('VelEcef', '<3f'),
            6: ('MagEcef', '<3f'),
            7: ('AccelEcef', '<3f'),
            8: ('LinearAccelEcef', '<3f'),
            9: ('PosU', '<f'),
            10: ('VelU', '<f')
        }
    }

    def __init__(self, port, baud):
        self._port = port
        self._baud = baud
        self._serial = None
        self._csum_mode = self.CHECKSUM_8BIT
        self._state = {}
        self._tstate = SortedDict()
        self._proc = None
        self._mode = self.MODE_SYNC
        self._offsetYPR = None
        self._autocal = True

        m = datetime.now()

        self._tstate[m] = {}
        d = self._tstate[m]

        for g in self.FIELDS:
            for f in self.FIELDS[g]:
                self._state[self.FIELDS[g][f][0]] = None
                d[self.FIELDS[g][f][0]] = None

    @staticmethod
    def create():
        port = findSerialDevice(VectorNav.DEFAULT_VID, VectorNav.DEFAULT_PID)

        if port is None:
            raise Exception('VectorNav FTDI converter not detected')

        return VectorNav(port, VectorNav.DEFAULT_BAUD)

    def calibrate(self):
        view = self.getView()

        self._offsetYPR = view.YPR

    def open(self, mode):
        self.close()

        if mode != self.MODE_SYNC and mode != self.MODE_ASYNC:
            return

        self._mode = mode

        if mode == self.MODE_ASYNC:
            self._queueState = mp.Queue()
            self._queueSync = mp.Queue()
            self._proc = mp.Process(target=self._processFunc)
            self._proc.start()
        else:
            self._queueState = None
            self._queueSync = None
            self._proc = None
            self._openPort()

    def close(self):
        if self._mode == self.MODE_ASYNC:
            if self._proc is None:
                return

            self._queueSync.put(None)
            self._syncState()
            self._proc.join()
            self._proc = None
        else:
            if self._serial is None:
                return

            self._closePort()

        self._queueState = None
        self._queueSync = None
        self._mode = self.MODE_SYNC

    def _openPort(self):
        self._serial = serial.Serial(self._port,
                                     self._baud,
                                     timeout=self.TIMEOUT)
        self._configBinaryOutput(0, 'TimeUTC', 'Ypr', 'PosLla', 'VelNed',
                                 'TimeUTC', 'NumSats', 'Fix', 'PosU', 'VelU',
                                 'TimeU')

    def _closePort(self):
        self._serial.close()
        self._serial = None

    def _processFunc(self):
        self._openPort()

        while True:
            try:
                try:
                    self._queueSync.get(False)
                    break
                except Queue.Empty:
                    pass

                self._recvResponse()

            except KeyboardInterrupt:  # Allow Ctrl+C without raising an exception
                break
            except:  # Allow other exceptions
                pass

        self._closePort()

    def _send(self, data):
        self._serial.write(data)

    def _recv(self, size):
        return self._serial.read(size)

    def _recvLine(self):
        return self._serial.readline()

    @staticmethod
    def _calcChecksum8(cmd):
        csum = 0

        for c in cmd:
            csum ^= ord(c)

        return '%02X' % (csum & 0xFF)

    @staticmethod
    def _appendCRC(crc, data):
        for c in data:
            crc = ((crc >> 8) & 0xFF) | (crc << 8)
            crc ^= ord(c) & 0xFF
            crc ^= (crc & 0xFF) >> 4
            crc ^= crc << 12
            crc ^= (crc & 0xFF) << 5

        return crc & 0xFFFF

    @staticmethod
    def _calcChecksum16(cmd):
        return '%04X' % VectorNav._appendCRC(VectorNav.INITIAL_CRC, cmd)

    def _calcChecksum(self, cmd):
        if self._csum_mode == self.CHECKSUM_16BIT:
            return VectorNav._calcChecksum16(cmd)
        return VectorNav._calcChecksum8(cmd)

    def _sendCommand(self, id, params):
        cmd = 'VN' + id

        if len(params) > 0:
            cmd += ',' + ','.join(str(p) for p in params)

        cmd = '$' + cmd + '*' + self._calcChecksum(cmd) + '\r\n'

        self._send(cmd)

    def process(self, flush=False):
        if self._mode == self.MODE_SYNC:
            if flush:
                self._serial.flushInput()  # Flush the input buffer

            self._waitResponse('$B')  # Wait for a binary response

    def _waitResponse(self, id, process_func=None):
        while True:
            r = self._recvResponse()

            if r:
                if id is not None and r[0] == id:
                    return process_func(
                        r) if process_func is not None else True
                elif r[0] == 'ERR':
                    return None

                return True

    def _processTextResponse(self, id, params):
        if id == 'GGA':
            pass

    def _recvResponse(self):
        sync = self._recv(1)

        if len(sync) < 1:
            return None

        if sync[0] == '$':
            line = str(self._recvLine())

            if line:
                return self._parseTextResponse(line)

        elif sync[0] == '\xFA':
            return self._parseBinaryResponse()

        return None

    def _parseTextResponse(self, line):
        if len(line) < 5:
            return None

        hdr = line[0:2]
        id = line[2:5]

        if hdr != 'VN':
            return None

        term = line.rfind('*')

        if term < 5:
            return None

        # TODO: Verify checksum

        params = line[5:term].split(',')

        self._processTextResponse(id, params)

        return (id, params)

    def _parseBinaryResponse(self):
        groups = self._recv(1)

        if len(groups) < 0:
            return None

        now = datetime.now()
        crc = VectorNav._appendCRC(self.INITIAL_CRC, groups)

        groups, = unpack('<B', groups)
        fields = []

        for g in range(0, 6):
            if (groups & (1 << g)) != 0:
                group_fields = self._recv(2)

                if len(group_fields) < 2:
                    return None

                crc = VectorNav._appendCRC(crc, group_fields)

                group_fields, = unpack('<H', group_fields)

                for f in range(0, 16):
                    if (group_fields & (1 << f)) != 0:
                        fields.append((g, f))

        state = {}

        for f in fields:
            if not f[0] in self.FIELDS:
                continue

            group_fields = self.FIELDS[f[0]]

            if not f[1] in group_fields:
                continue

            field_info = group_fields[f[1]]

            size = calcsize(field_info[1])
            data = self._recv(size)

            if len(data) < size:
                return None

            crc = VectorNav._appendCRC(crc, data)
            state[field_info[0]] = unpack(field_info[1], data)

        # Receive the CRC
        ref_crc = self._recv(2)
        crc = VectorNav._appendCRC(crc, ref_crc)

        if crc != 0:
            return None  # Bad CRC

        for st in state:
            self._setState(st, state[st], now)

        return ('$B', None)

    def _setState(self, id, value, timestamp):
        state = (id, value, datetime.now())

        if self._mode == self.MODE_ASYNC:
            self._queueState.put(state)
        else:
            self._updateState(state)

    def _syncState(self):
        if self._mode != self.MODE_ASYNC:
            return

        # Add new entries
        try:
            while True:
                self._updateState(self._queueState.get(False))

        except Queue.Empty:
            pass

        # Remove old entries
        keys = list(
            self._tstate.irange(minimum=None,
                                maximum=datetime.now() - self.STATE_TIMEOUT,
                                inclusive=(True, False)))

        for k in keys:
            try:
                del self._tstate[k]
            except:
                pass

    def _updateState(self, state):
        self._state[state[0]] = state[1]
        d = self._tstate.setdefault(state[2], {})
        d[state[0]] = state[1]

    def _registerRead(self, reg_id):
        self._sendCommand('RRG', [reg_id])
        return self._waitResponse('RRG', lambda r: r[1])

    def _registerWrite(self, reg_id, *args):
        params = [reg_id]
        params.extend(list(args))

        self._sendCommand('WRG', params)
        return self._waitResponse('WRG', lambda r: r[1])

    def _findField(self, id):
        for g in self.FIELDS:
            for f in self.FIELDS[g]:
                if self.FIELDS[g][f][0] == id:
                    return (g, f)

        return None

    def _configBinaryOutput(self, index, *fields):
        if index < 0 or index > 2:
            return

        groups = 0
        group_fields = [0, 0, 0, 0, 0, 0]

        for f in fields:
            field = self._findField(f)

            if field is None:
                continue

            groups |= 1 << field[0]
            group_fields[field[0]] |= 1 << field[1]

        args = [1, self.BINARY_OUTPUT_RATE_DIV, '%02X' % groups]
        args.extend('%04X' % x for x in filter(None, group_fields))
        args = tuple(args)

        self._registerWrite(self.REG_BIN_OUT_1 + index, *args)

    def getState(self, id, maxTime=None):
        self._syncState()

        keys = self._tstate.irange(minimum=None,
                                   maximum=maxTime,
                                   inclusive=(True, True),
                                   reverse=True)

        for k in keys:
            if id in self._tstate[k]:
                value = self._tstate[k][id]

                if id == 'Ypr' and value is not None:
                    if self._offsetYPR is not None:
                        value = (value[0] - self._offsetYPR[0],
                                 value[1] - self._offsetYPR[1],
                                 value[2] - self._offsetYPR[2])
                    elif self._autocal:
                        self._offsetYPR = value
                        value = (0, 0, 0)

                if value is not None and len(value) == 1:
                    value = value[0]

                return value

        return None

    def getView(self, timestamp=None):
        if len(self._tstate) == 0:
            return None

        try:
            if timestamp is None:
                maxTime = self._tstate.iloc[-1]
            else:
                maxTime = next(
                    self._tstate.irange(minimum=None,
                                        maximum=timestamp,
                                        inclusive=(True, True),
                                        reverse=True))

            return VectorNav.View(self, maxTime)
        except:
            #log.log('Error getting VectorNav view for timestamp {time}'.format(time=timestamp))
            print 'Error getting VectorNav view for timestamp {time}'.format(
                time=timestamp)
            return None

    class View(object):
        def __init__(self, vn, maxTime):
            self._vn = vn
            self._maxTime = maxTime

        def getState(self, id):
            return self._vn.getState(id, self._maxTime)

        @property
        def timeUTC(self):
            t = self.getState('TimeUTC')

            time = t[0]

            if time is None:
                return None

            return (datetime(2000 + time[0], time[1] + 1, time[2] + 1, time[3],
                             time[4], time[5], time[6] * 1000), t[1])

        @property
        def position(self):
            """Get the latest position as (latitude, longitude, altitude)"""
            return self.getState('PosLla')

        @property
        def velocity(self):
            """Get the latest velocity in NED frame in m/s"""
            return self.getState('VelNed')

        @property
        def YPR(self):
            """Get the latest yaw/pitch/roll values in degrees"""
            return self.getState('Ypr')

        @property
        def numSats(self):
            """Get the latest number of visible satellites"""
            return self.getState('NumSats')

        @property
        def fix(self):
            """Get the latest GPS fix type"""
            return self.getState('Fix')

        @property
        def dict(self):
            """Get the data dictionary"""
            d = {
                'yaw': 0,
                'pitch': 0,
                'roll': 0,
                'src_att': None,
                'hdg': 0,
                'src_hdg': None,
                'lat': 0,
                'lon': 0,
                'relative_alt': 0,
                'src_gps': None,
                'cog': 0,
                'src_cog': None
            }

            ypr = self.YPR
            pos = self.position
            vel = self.velocity

            if ypr is not None:
                d['yaw'] = math.radians(ypr[0])
                d['pitch'] = math.radians(ypr[1])
                d['roll'] = math.radians(ypr[2])
                d['hdg'] = ypr[0] * 100
                d['src_att'] = VectorNav.SRC_VECTORNAV
                d['src_hdg'] = VectorNav.SRC_VECTORNAV

            if pos is not None and abs(pos[0]) > 1.0 and abs(pos[1]) > 1.0:
                d['lat'] = pos[0] * 1e7
                d['lon'] = pos[1] * 1e7
                d['relative_alt'] = pos[2] * 1e3
                d['src_gps'] = VectorNav.SRC_VECTORNAV

            d['fd_timestamp'] = self._maxTime.strftime(TIMESTAMP_SIGNATURE)
            #d['cog'] = atan2(vel[1], vel[0])
            d['src'] = VectorNav.SRC_VECTORNAV

            return d

        @property
        def json(self):
            """Get the json-encoded data"""
            return json.dumps(self.dict)
Пример #14
0
class TimeSeries(TictsMagicMixin, TictsOperationMixin, PandasMixin,
                 TictsIOMixin, TictsPlot):
    """ TimeSeries object.

    Args:
        default: The default value of timeseries.
        permissive (bool): Whether to allow accessing non-existing values or not.
            If is True, getting non existing item returns None.
            If is False, getting non existing item raises.
    """
    _default_interpolate = "previous"

    _meta_keys = ('default', 'name', 'permissive')

    @property
    def index(self):
        return self.data.keys()

    @property
    def lower_bound(self):
        """Return the lower bound time index."""
        if self.empty:
            return MINTS
        return self.index[0]

    @property
    def upper_bound(self):
        """Return the upper bound time index."""
        if self.empty:
            return MAXTS
        return self.index[-1]

    @property
    def _has_default(self):
        return self.default != NO_DEFAULT

    @property
    def _kwargs_special_keys(self):
        kwargs = {}
        for attr_name in self._meta_keys:
            kwargs[attr_name] = getattr(self, attr_name)
        return kwargs

    @property
    def empty(self):
        """Return whether the TimeSeries is empty or not."""
        return len(self) == 0

    def __init__(self,
                 data=None,
                 default=NO_DEFAULT,
                 name=DEFAULT_NAME,
                 permissive=True,
                 tz='UTC'):
        """"""
        if isinstance(data, self.__class__):
            for attr in ('data', *self._meta_keys):
                setattr(self, attr, getattr(data, attr))

            # Only set 'default' and 'name' if is different from default
            if default != NO_DEFAULT:
                setattr(self, 'default', default)
            if name != DEFAULT_NAME:
                setattr(self, 'name', name)
            return

        if hasattr(default, 'lower') and default.lower() == 'no_default':
            # 'no_default' as string is used at JSON serealization time
            self.default = NO_DEFAULT
        else:
            self.default = default

        self.name = name
        self.permissive = permissive

        # Overwrite the name if data is an instance of pd.DataFrame or pd.Series
        if isinstance(data, pd.DataFrame):
            if len(data.columns) != 1:
                msg = ("Can't convert a DataFrame with several columns into "
                       "one timeseries: {}.")
                raise ValueError(msg.format(data.columns))
            self.name = data.columns[0]

        elif isinstance(data, pd.Series):
            self.name = data.name

        try:
            tz = pytz.timezone(tz)
        except pytz.UnknownTimeZoneError:
            raise ValueError('{} is not a valid timezone'.format(tz))

        # SortedDict.__init__ does not use the __setitem__
        # Hence we got to parse datetime keys ourselves.
        # SortedDict use the first arg given and check if is a callable
        # in case you want to give your custom sorting function.
        self.data = SortedDict(None, _process_args(data, tz))

    def __setitem__(self, key, value):
        if isinstance(key, slice):
            return self.set_interval(key.start, key.stop, value)
        if key in self._meta_keys:
            super().__setitem__(key, value)
        else:
            key = timestamp_converter(key, self.tz)
            self.data[key] = value

    def __getitem__(self, key):
        """Get the value of the time series, even in-between measured values by interpolation.
        Args:
            key (datetime): datetime index
            interpolate (str): interpolate operator among ["previous", "linear"]
        """

        interpolate = self._default_interpolate

        if isinstance(key, tuple):
            if len(key) == 2:
                key, interpolate = key
            elif len(key) > 2:
                raise KeyError

        if isinstance(key, slice):
            return self.slice(key.start, key.stop)

        key = timestamp_converter(key, self.tz)

        basemsg = "Getting {} but default attribute is not set".format(key)
        if self.empty:
            if self._has_default:
                return self.default
            else:
                if self.permissive:
                    return
                else:
                    raise KeyError(
                        "{} and timeseries is empty".format(basemsg))

        if key < self.lower_bound:
            if self._has_default:
                return self.default
            else:
                if self.permissive:
                    return
                else:
                    msg = "{}, can't deduce value before the oldest measurement"
                    raise KeyError(msg.format(basemsg))

        # If the key is already defined:
        if key in self.index:
            return self.data[key]

        if interpolate.lower() == "previous":
            fn = self._get_previous
        elif interpolate.lower() == "linear":
            fn = self._get_linear_interpolate
        else:
            raise ValueError("'{}' interpolation unknown.".format(interpolate))

        return fn(key)

    def _get_previous(self, time):
        # In this case, bisect_left == bisect_right == bisect
        # And idx > 0 as we already handled other cases
        previous_idx = self.data.bisect(time) - 1
        time_idx = self.index[previous_idx]
        return self.data[time_idx]

    def _get_linear_interpolate(self, time):
        # TODO: put it into a 'get_previous_index' method
        idx = self.data.bisect_left(time)
        previous_time_idx = self.index[idx - 1]

        # TODO: check on left bound case

        # out of right bound case:
        if idx == len(self):
            return self.data[previous_time_idx]

        next_time_idx = self.index[idx]

        previous_value = self.data[previous_time_idx]
        next_value = self.data[next_time_idx]

        coeff = (time - previous_time_idx) / (
            next_time_idx - previous_time_idx)

        value = previous_value + coeff * (next_value - previous_value)
        return value

    def slice(self, start, end):  # noqa A003
        """Slice your timeseries for give interval.

        Args:
            start (datetime or str): lower bound
            end (datetime or str): upper bound

        Returns:
            TimeSeries sliced
        """
        start = timestamp_converter(start, self.tz)
        end = timestamp_converter(end, self.tz)

        newts = TimeSeries(**self._kwargs_special_keys)

        for key in self.data.irange(start, end, inclusive=(True, False)):
            newts[key] = self[key]

        should_add_left_closure = (start not in newts.index
                                   and start >= self.lower_bound)
        if should_add_left_closure:
            newts[start] = self[start]  # is applying get_previous on self

        return newts

    def set_interval(self, start, end, value):
        """Set a value for an interval of time.

        Args:
            start (datetime or str): lower bound
            end (datetime or str): upper bound
            value: the value to be set

        Returns:
            self

        Raises:
            NotImplementedError: when no default is set.
        """
        if not self._has_default:
            msg = "At the moment, you have to set a default for set_interval"
            raise NotImplementedError(msg)

        start = timestamp_converter(start, self.tz)
        end = timestamp_converter(end, self.tz)

        keys = self.data.irange(start, end, inclusive=(True, False))

        last_value = self[end]

        for key in list(keys):
            del self.data[key]

        self[start] = value
        self[end] = last_value

    def compact(self):
        """Convert this instance to a compact version: consecutive measurement of the
        same value are discarded.

        Returns:
            TimeSeries
        """
        ts = TimeSeries(**self._kwargs_special_keys)
        for time, value in self.items():
            should_set_it = ts.empty or (ts[time] != value)
            if should_set_it:
                ts[time] = value
        return ts

    def iterintervals(self, end=None):
        """Iterator that contain start, end of intervals.

        Args:
            end (datetime): right bound of last interval.
        """
        lst_keys = SortedList(self.index)
        if not end:
            end = self.upper_bound
        else:
            end = timestamp_converter(end, self.tz)
            if end not in lst_keys:
                lst_keys.add(end)

        for i, key in enumerate(lst_keys[:-1]):
            next_key = lst_keys[i + 1]
            if next_key > end:  # stop there
                raise StopIteration
            yield key, next_key

    def equals(self, other, check_default=True, check_name=True):
        if not isinstance(other, self.__class__):
            raise TypeError("Can't compare {} with {}".format(
                self.__class__.__name__, other.__class__.__name__))

        is_equal = self.data == other.data

        if check_default:
            is_equal = is_equal and self.default == other.default

        if check_name:
            is_equal = is_equal and self.name == other.name

        return is_equal

    @property
    def tz(self):
        if self.empty:
            return pytz.UTC
        return str(self.index[0].tz)

    def tz_convert(self, tz):
        try:
            tz = pytz.timezone(tz)
        except pytz.UnknownTimeZoneError:
            raise ValueError('{} is not a valid timezone'.format(tz))

        ts = deepcopy(self)

        for key in ts.index:
            ts[key.tz_convert(tz)] = ts.data.pop(key)

        return ts
Пример #15
0
class LogListener(threading.Thread):
    lock = threading.Lock()
    logfile = open('./test.log', 'w')

    def __init__(self, name, default_timeout):
        threading.Thread.__init__(self)

        self.messages = SortedDict()
        self.default_timeout = default_timeout
        self.event = threading.Event()

        self.is_running = True
        self.name = name
        self.daemon = True
        self.start()

    def starts_with_count(self, message):
        count = 0
        for i in self.messages.irange(minimum=message):
            if not i.startswith(message):
                break
            count += self.messages[i]
        return count

    def exact_count(self, message):
        return self.messages.get(message, 0)

    def expect_count(self, message, mincount, timeout=None):
        if timeout is None:
            timeout = self.default_timeout

        now = time.time()
        end = now + timeout
        curcount = self.exact_count(message)

        while (curcount < mincount and now < end):
            self.event.wait(end - now)
            now = time.time()
            curcount = self.exact_count(message)

        return curcount

    def expect(self, message, expected_count, timeout=None):
        count = self.expect_count(message, expected_count, timeout)
        if count != expected_count:
            print(
                'TEST FAILED: message:"{0}", count:{1} (expected {2})'.format(
                    message, count, expected_count))
            traceback.print_stack()
            sys.exit()
        else:
            print('{3}: message:"{0}", count:{1} (expected {2})'.format(
                message, count, expected_count, self.name))

    def expect_minimum(self, message, mincount, timeout=None):
        count = self.expect_count(message, mincount, timeout)
        if count < mincount:
            print(
                'TEST FAILED: message:"{0}" count:{1} (expected at least {2})'.
                format(message, count, mincount))
            traceback.print_stack()
            sys.exit()
        else:
            print(
                '{3}: message:"{0}" count:{1} (expected at least {2})'.format(
                    message, count, mincount, self.name))

    def expect_maximum(self, message, maxcount, timeout=None):
        count = self.expect_count(message, maxcount + 1, timeout)
        if count > maxcount:
            print(
                'TEST FAILED: message:"{0}" count:{1} (expected no more than {2})'
                .format(message, count, maxcount))
            traceback.print_stack()
            sys.exit()
        else:
            print('{3}: message:"{0}" count:{1} (expected no more than {2})'.
                  format(message, count, maxcount, self.name))

    def run(self):
        try:
            os.mkfifo(self.name)
        except OSError as oe:
            if oe.errno != errno.EEXIST:
                raise

        while self.is_running:
            with open(self.name) as fifo:
                with LogListener.lock:
                    print("connected to {0}".format(self.name))

                while self.is_running:
                    line = fifo.readline()

                    if line == '':
                        break

                    key = line[:-1]
                    with LogListener.lock:
                        LogListener.logfile.write('{0}: {1}: "{2}"\n'.format(
                            time.strftime('%X %x %Z'), self.name, key))
                        LogListener.logfile.flush()

                    self.messages[key] = self.messages.get(key, 0) + 1

                    self.event.set()
                    self.event.clear()

    def close(self):
        self.is_running = False
        self.join()
Пример #16
0
class FreshPondSim:
    def __init__(self,
                 distance,
                 start_time,
                 end_time,
                 entrances,
                 entrance_weights,
                 rand_velocities_and_distances_func,
                 entrance_rate,
                 entrance_rate_integral=None,
                 entrance_rate_integral_inverse=None,
                 interpolate_rate=True,
                 interpolate_rate_integral=True,
                 interpolate_res=None,
                 snap_exit=True):
        assert_positive_real(distance, 'distance')
        assert_real(start_time, 'start_time')
        assert_real(end_time, 'end_time')
        if not (start_time < end_time):
            raise ValueError(f"start_time should be less than end_time")
        assert len(entrances) == len(entrance_weights)
        self.start_time = start_time
        self.end_time = end_time
        self.dist_around = distance
        self.entrances = entrances
        self.entrance_weights = entrance_weights
        self.rand_velocities_and_distances = rand_velocities_and_distances_func
        self._snap_exit = snap_exit

        if interpolate_rate or interpolate_rate_integral:
            if interpolate_res is None:
                raise ValueError("Specify interpolate_res for interpolation")

        if interpolate_rate:
            self.entrance_rate = DynamicBoundedInterpolator(
                entrance_rate, start_time, end_time, interpolate_res)
        else:
            self.entrance_rate = entrance_rate

        if interpolate_rate_integral: # Want to interplate the integral function
            if entrance_rate_integral is None: # No integral function given
                # Do numerical integration and interpolate to speed it up
                def integral_func(t):
                    y, abserr = integrate.quad(entrance_rate, start_time, t)
                    return y

                self.entrance_rate_integral = DynamicBoundedInterpolator(
                    integral_func, start_time, end_time, interpolate_res)
            else: # Integral function was provided
                # Use the provided rate integral function but interpolate it
                self.entrance_rate_integral = DynamicBoundedInterpolator(
                    entrance_rate_integral, start_time, end_time, interpolate_res)
        else: # Don't want to interpolate the integral function
            # If entrance_rate_integral is not None (i.e. is provided) then
            # that function will be used as the rate integral.
            # If entrance_rate_integral is None, numerical integration will
            # be used.
            self.entrance_rate_integral = entrance_rate_integral

        self.entrance_rate_integral_inverse = entrance_rate_integral_inverse

        self.pedestrians = SortedKeyList(key=attrgetter('start_time'))
        
        self._counts = SortedDict()
        self._counts[self.start_time] = 0

        self._counts_are_correct = True

        self.refresh_pedestrians()

    def _distance(self, a, b):
        """signed distance of a relative to b"""
        return circular_diff(a % self.dist_around, b % self.dist_around,
                             self.dist_around)

    def _distance_from(self, b):
        """returns a function that returns the signed sitance from b"""
        return lambda a: self._distance(a, b)

    def _abs_distance_from(self, b):
        """returns a function that returns the distance from b"""
        return lambda a: abs(self._distance(a, b))

    def _closest_exit(self, dist):
        """Returns the closest number to dist that is equivalent mod dist_around
        to an element of entrances"""
        closest_exit = min(self.entrances, key=self._abs_distance_from(dist))
        diff = self._distance(closest_exit, dist)
        corrected_dist = dist + diff
        return corrected_dist

    def refresh_pedestrians(self):
        """Refreshes the pedestrians in the simulation to random ones"""
        self.clear_pedestrians()

        start_times = list(
            random_times(self.start_time, self.end_time,
                         self.entrance_rate,
                         self.entrance_rate_integral,
                         self.entrance_rate_integral_inverse))
        n_pedestrians = len(start_times)
        entrances = random.choices(population=self.entrances,
                                   weights=self.entrance_weights,
                                   k=n_pedestrians)
        velocities, distances = self.rand_velocities_and_distances(
            n_pedestrians).T

        def pedestrians_generator():
            for start_time, entrance, velocity, dist in zip(
                    start_times, entrances, velocities, distances):
                assert dist > 0
                if self._snap_exit:
                    original_exit = entrance + dist * sign(velocity)
                    corrected_exit = self._closest_exit(original_exit)
                    corrected_dist = abs(corrected_exit - entrance)
                    if math.isclose(corrected_dist, 0, abs_tol=1e-10):
                        corrected_dist = self.dist_around
                else:
                    corrected_dist = dist
                yield FreshPondPedestrian(self.dist_around, entrance,
                                          corrected_dist, start_time, velocity)

        self.add_pedestrians(pedestrians_generator())

    def clear_pedestrians(self):
        """Removes all pedestrains in the simulation"""
        self.pedestrians.clear()
        self._reset_counts()
        self._counts_are_correct = True

    def add_pedestrians(self, pedestrians):
        """Adds all the given pedestrians to the simulation"""
        def checked_pedestrians():
            for p in pedestrians:
                self._assert_pedestrian_in_range(p)
                yield p

        initial_num_pedestrians = self.num_pedestrians()
        self.pedestrians.update(checked_pedestrians())
        final_num_pedestrians = self.num_pedestrians()

        if final_num_pedestrians > initial_num_pedestrians:
            self._counts_are_correct = False
        else:
            assert final_num_pedestrians == initial_num_pedestrians

    def _assert_pedestrian_in_range(self, p):
        """Makes sure the pedestrian's start time is in the simulation's
        time interval"""
        if not (self.start_time <= p.start_time < self.end_time):
            raise ValueError(
                "Pedestrian start time is not in range [start_time, end_time)")

    def add_pedestrian(self, p):
        """Adds a new pedestrian to the simulation"""
        self._assert_pedestrian_in_range(p)
        self.pedestrians.add(p)

        # Update counts only when counts are correct
        if self._counts_are_correct:
            # add a new breakpoint at the pedestrian's start time if it not there
            self._counts[p.start_time] = self.n_people(p.start_time)

            # add a new breakpoint at the pedestrian's end time if it not there
            self._counts[p.end_time] = self.n_people(p.end_time)

            # increment all the counts in the pedestrian's interval of time
            # inclusive on the left, exclusive on the right
            # If it were inclusive on the right, then the count would be one more
            # than it should be in the period after end_time and before the next
            # breakpoint after end_time
            for t in self._counts.irange(p.start_time,
                                        p.end_time,
                                        inclusive=(True, False)):
                self._counts[t] += 1

    def _reset_counts(self):
        """Clears _counts and sets count at start_time to 0"""
        self._counts.clear()
        self._counts[self.start_time] = 0

    def _recompute_counts(self):
        """Store how many people there are whenever someone enters or exits so
        the number of people at a given time can be found quickly later"""
        # print("Recomputing counts")
        self._reset_counts()

        if self.num_pedestrians() == 0:
            return

        # pedestrians are already sorted by start time
        start_times = [p.start_time for p in self.pedestrians]
        end_times = sorted([p.end_time for p in self.pedestrians])

        n = len(start_times)
        curr_count = 0  # current number of people
        start_times_index = 0
        end_times_index = 0
        starts_done = False  # whether all the start times have been added
        ends_done = False  # whether all the end times have been added
        while not (starts_done and ends_done):
            # determine whether a start time or an end time should be added next
            # store this in the variable take_start which is true if a start
            # time should be added next
            if starts_done:
                # already added all the start times; add an end time
                take_start = False
            elif ends_done:
                # already added all the end times; add a start time
                take_start = True
            else:
                # didn't add all the end times nor all the start times
                # add the time that is earliest
                next_start_time = start_times[start_times_index]
                next_end_time = end_times[end_times_index]
                take_start = next_start_time < next_end_time

            if take_start:
                # add next start
                curr_count += 1
                start_time = start_times[start_times_index]
                self._counts[start_time] = curr_count
                start_times_index += 1
                if start_times_index == n:
                    starts_done = True
            else:
                # add next end
                curr_count -= 1
                end_time = end_times[end_times_index]
                self._counts[end_time] = curr_count
                end_times_index += 1
                if end_times_index == n:
                    ends_done = True

    def n_unique_people_saw(self, p):
        """Returns the number of unique people that a pedestrian sees"""
        n = 0
        for q in self.pedestrians:
            if p.intersects(q):
                n += 1
        return n

    def n_people_saw(self, p):
        """Returns the number of times a pedestrian sees someone"""
        n = 0
        for q in self.pedestrians:
            if p.end_time > q.start_time and p.start_time < q.end_time:
                n += p.n_intersections(q)
        return n

    def intersection_directions(self, p):
        """Returns the number of people seen going in the same direction and the
        number of people seen going in the opposite direction by p as a tuple"""
        n_same, n_diff = 0, 0
        for q in self.pedestrians:
            if p.end_time > q.start_time and p.start_time < q.end_time:
                d = q.intersection_direction(p)
                if d == 1:
                    n_same += 1
                elif d == -1:
                    n_diff += 1
        return n_same, n_diff

    def intersection_directions_total(self, p):
        n_same, n_diff = 0, 0
        for q in self.pedestrians:
            if p.end_time > q.start_time and p.start_time < q.end_time:
                i = p.total_intersection_direction(q)
                if i < 0:
                    n_diff += -i
                elif i > 0:
                    n_same += i
        return n_same, n_diff

    def n_people(self, t):
        """Returns the number of people at a given time"""

        if not self._counts_are_correct:
            self._recompute_counts()
            self._counts_are_correct = True

        if t in self._counts:
            return self._counts[t]
        elif t < self.start_time:
            return 0
        else:
            index = self._counts.bisect_left(t)
            return self._counts.values()[index - 1]

    def num_pedestrians(self):
        """Returns the total number of pedestrians in the simulation"""
        return len(self.pedestrians)

    def get_pedestrians_in_interval(self, start, stop):
        """Returns a list of all the pedestrians who entered in the interval
        [start, stop]"""
        return list(self.pedestrians.irange_key(start, stop))

    def num_entrances_in_interval(self, start, stop):
        """Returns the number of pedestrians who entered in the given interval
        of time [start, stop]"""
        return len(self.get_pedestrians_in_interval(start, stop))
    
    def get_enter_and_exit_times_in_interval(self, start, stop):
        """Returns the entrance and exit times in a given time interval
        as a tuple of lists (entrance_times, exit_times)."""
        start_times = []
        end_times = []
        for p in self.pedestrians:
            if start <= p.start_time <= stop:
                start_times.append(p.start_time)
            if start <= p.end_time <= stop:
                end_times.append(p.end_time)
        return start_times, end_times
    
    def get_pedestrians_at_time(self, t):
        """Returns a list of all the pedestrians who were there at time t"""
        # get all pedestrians who entered at or before time t
        entered_before_t = self.pedestrians.irange_key(
            min_key=None, max_key=t, inclusive=(True, True))
        # Of those, return return the ones who exited after time t
        return [p for p in entered_before_t if p.end_time > t]
Пример #17
0
class TxGraph(object):
    """represents a graph of all transactions
    within the current window

    Attributes:
        median(float)   :   the current median of the degree of the nodes
        highMarker(int) :   the latest timestamp seen so far
        lowMarker(int)  :   the earliest timestamp of the window we are
                            interested in
        txMap(dict)     :   this is a collection of EdgeList's with key being
                            the timestamp and the value an instance of EdgeList
        edgeMap(dict)   :   this is collection of all Edges within a window
                            with key being the name of an Edge
        nodeMap(dict)   :   this represents a collection of Nodes with a window
                            with key being the name of the Node
        degreeList(list):   list of degrees of noded (sorted)
    """

    WINDOW_SIZE = 60
    def __init__(self):
        self.median = 0
        self.highMarker = TxGraph.WINDOW_SIZE
        self.lowMarker = 1
        self.txMap = SortedDict() #sorted by unix epoch (timestamp)
        self.edgeMap = SortedDict() #sorted by edge name
        self.nodeMap = SortedDict() #sorted by node name
        self.degreeList = SortedList() #sorted by degreeList
    
    def __calculate_median(self, use_existing_list=False):
        """calculates median by adding degrees to a sortedlist
        """
        if not use_existing_list:
            #lets reconstruct the list
            self.degreeList = SortedList()
        
            for node in self.nodeMap.itervalues():
                if node.degree > 0:
                    self.degreeList.add(node.degree)

        listLen = len(self.degreeList)
        if listLen == 0:
            raise Exception("No items in the degreeList")

        if listLen == 1:
            return self.degreeList[0]/1.0

        if (listLen % 2) == 0: 
            return (self.degreeList[listLen/2] + self.degreeList[(listLen/2) - 1]) / 2.0
        
        return self.degreeList[listLen/2]/1.0

    
    def __get_edgelist(self, tstamp, create=True):
        """returns an instance of EdgeList with matching
        timestamp and creates one if needed
        """
        edgeList = self.txMap.get(tstamp, None)
        if edgeList is None and create is True:
            edgeList = EdgeList(tstamp)
            self.txMap[tstamp] = edgeList
        return edgeList

    
    def __getnode_with_name(self, name, create=True):
        """returns an instance of Node with matching name
        and creates one if necessary

        Args:
            name(str)   :   name of the edge
            create(bool):   flag to indicate whether to create a 
                            missing node
        """
        
        node = self.nodeMap.get(name, None)
        if node is None and create is True:
            node = Node(name)
            self.nodeMap[name] = node
        return node

    
    def __incr_degree_of_edge_nodes(self, edge):
        """increments the degree of the two nodes
        of an edge
        """

        src = self.__getnode_with_name(edge.source)
        src.incr_degree()
        
        tar = self.__getnode_with_name(edge.target)
        tar.incr_degree()
   
        return (src.degree, tar.degree)
    
    def __decr_degree_of_edge_nodes(self, edge):
        """decrements the degree of the two nodes
        of an edge
        """
        
        self.__decr_degree_of_node(edge.source)
        self.__decr_degree_of_node(edge.target)
   
    
    def __decr_degree_of_node(self, name):
        """decrements the degree of a node
        and removes it from the nodeMap if degree is 0
        """
        
        node = self.__getnode_with_name(name, create=False)
        node.decr_degree()
        
        if node.degree == 0:
            del self.nodeMap[node.name]


    def __remove_edge(self, edge):
        """removes an edge from the graph and updates the 
        degree of a node. If degree of a node goes to 0, then
        remove the node as well
        
        Args:
            egde(Edge)   :   An instance of Edge class
        """

        self.__decr_degree_of_edge_nodes(edge)
        del self.edgeMap[edge.name]

    
    def __update_tstamp_for_existing_edge(self, edgeName, tstamp):
        """updates the timestamp for an existing edge and moves
        the edge to an appropriate EdgeList
        
        Args:
            edgeName(str)   :   name of the edge to be updated
            tstamp(int)     :   unix epoch of the timstamp
        """
        
        currEdge = self.edgeMap[edgeName]
        if not currEdge:
            return
        
        if tstamp <= currEdge.tstamp:
            return #ignore older transactions within the window
        
        #remove the edge from the edgelist with old timestamp
        edgeList = self.__get_edgelist(currEdge.tstamp, create=False)
        del edgeList.edges[currEdge.name]

        #update the tstamp in the edge
        currEdge.tstamp = tstamp

        #move this edge to the correct edgelist
        edgeList = self.__get_edgelist(tstamp)
        edgeList.edges[currEdge.name] = currEdge

    
    def __update_tx_window(self):
        """updates the transaction window of the graph
        This method is called when a newer transaction out the 
        window arrives. It does the following:
        1. Gets the edgeList's that are below the lowMarker
        2. Goes through the edges and deletes them from the edgeMap
        3. Update the degree of the nodes
        4. Moves the window by deleting the stale edgeLists
        """
        tsIter = self.txMap.irange(None, self.lowMarker, inclusive=(True,False))
        lastTStamp = None
        for tstamp in tsIter:
            lastTStamp  = tstamp
            edgeList = self.txMap[tstamp]
        
            for edge in edgeList.edges.itervalues():
                self.__remove_edge(edge)

        #lets delete the stale edgelists
        if lastTStamp:
            lowIdx = self.txMap.index(lastTStamp)
            del self.txMap.iloc[:lowIdx+1]
    
    
    def process_transaction(self, tstamp, source, target):
        """this is the starting point of transaction processing.
        We first check whether the tx is within the window.
        If it is, then we update the Edge (if it already exists) or 
        create a new Edge if necessary and update the median.
        If the tx is not within the window and is newer, we then
        move the window and remove all stale(older) edges and create
        a new edge for the newer transaction and finally update the
        median
        """
        
        #basic sanity checks
        if source is None or target is None:
            raise Exception("Invalid node")

        if len(source) == 0 or len(target) == 0:
            raise Exception("Invalid node")

        if source == target:
            raise Exception("source and target cannot be the same")
        
        #timestamp of the transaction is old and can be ignored
        if tstamp < self.lowMarker:
            return

        #create a new edge representing this transaction     
        newEdge = Edge(tstamp, source, target)
        
        if tstamp <= self.highMarker:
            if newEdge.name in self.edgeMap:
                self.__update_tstamp_for_existing_edge(newEdge.name, tstamp)
                #no need to recalculate the median here since degree does not change
                return
            
            """handle new edge
            1. find the edgelist with the same timestamp (if not create it)
            2. add this edge to the edgelist and edgemap
            4. create new Nodes for the edges if needed or update their degrees
            5. update the degreeList with the new degrees
            6. recalculate the median but use the existing degreeList
            """
            edgeList = self.__get_edgelist(tstamp)
            edgeList.edges[newEdge.name] = newEdge
            self.edgeMap[newEdge.name] = newEdge

            """
            this is optimization because most of the degrees of the 
            nodes hasn't changed and therefore we can reuse the existing list
            """
            srcDegree, tarDegree = self.__incr_degree_of_edge_nodes(newEdge)
            if srcDegree == 1:
                self.degreeList.add(1)
            else:
                self.degreeList.remove(srcDegree - 1)
                self.degreeList.add(srcDegree)

            if tarDegree == 1:
                self.degreeList.add(1)
            else:
                self.degreeList.remove(tarDegree - 1)
                self.degreeList.add(tarDegree)

            self.median = self.__calculate_median(use_existing_list=True)
            return

        """this transaction is newer and we need to move the window
        1. update the low and high markers of the timestamp window
        2. create edgelist with this newer timestamp
        2. add the new edge to the edgelist
        3. add the new edge to the edgemap
        4. create new Nodes of the edges if needed or update their degrees
        5. calculate the median (but reconstruct the degreeList) 
        """
        #this tx is newer and we need to move the window
        self.highMarker = tstamp
        self.lowMarker = tstamp - TxGraph.WINDOW_SIZE + 1

        self.__update_tx_window()
        
        if newEdge.name in self.edgeMap:
            self.__update_tstamp_for_existing_edge(newEdge.name, tstamp)
        else:
            edgeList = self.__get_edgelist(tstamp)
            edgeList.edges[newEdge.name] = newEdge
            self.edgeMap[newEdge.name] = newEdge
            self.__incr_degree_of_edge_nodes(newEdge)

        self.median = self.__calculate_median()
Пример #18
0
class RegionMap(object):
    """
    Mostly used in SimAbstractMemory, RegionMap stores a series of mappings between concrete memory address ranges and
    memory regions, like stack frames and heap regions.
    """

    def __init__(self, is_stack):
        """
        Constructor

        :param is_stack:    Whether this is a region map for stack frames or not. Different strategies apply for stack
                            regions.
        """
        self.is_stack = is_stack

        # A sorted list, which maps stack addresses to region IDs
        self._address_to_region_id = SortedDict()
        # A dict, which maps region IDs to memory address ranges
        self._region_id_to_address = { }

    #
    # Properties
    #

    def __repr__(self):
        return "RegionMap<%s>" % (
            "S" if self.is_stack else "H"
        )

    @property
    def is_empty(self):
        return len(self._address_to_region_id) == 0

    @property
    def stack_base(self):
        if not self.is_stack:
            raise SimRegionMapError('Calling "stack_base" on a non-stack region map.')

        return next(self._address_to_region_id.irange(reverse=True))

    @property
    def region_ids(self):
        return self._region_id_to_address.keys()

    #
    # Public methods
    #

    @SimStatePlugin.memo
    def copy(self, memo): # pylint: disable=unused-argument
        r = RegionMap(is_stack=self.is_stack)

        # A shallow copy should be enough, since we never modify any RegionDescriptor object in-place
        r._address_to_region_id = self._address_to_region_id.copy()
        r._region_id_to_address = self._region_id_to_address.copy()

        return r

    def map(self, absolute_address, region_id, related_function_address=None):
        """
        Add a mapping between an absolute address and a region ID. If this is a stack region map, all stack regions
        beyond (lower than) this newly added regions will be discarded.

        :param absolute_address:            An absolute memory address.
        :param region_id:                   ID of the memory region.
        :param related_function_address:    A related function address, mostly used for stack regions.
        """

        if self.is_stack:
            # Sanity check
            if not region_id.startswith('stack_'):
                raise SimRegionMapError('Received a non-stack memory ID "%d" in a stack region map' % region_id)

            # Remove all stack regions that are lower than the one to add
            while True:
                try:
                    addr = next(self._address_to_region_id.irange(maximum=absolute_address, reverse=True))
                    descriptor = self._address_to_region_id[addr]
                    # Remove this mapping
                    del self._address_to_region_id[addr]
                    # Remove this region ID from the other mapping
                    del self._region_id_to_address[descriptor.region_id]
                except StopIteration:
                    break

        else:
            if absolute_address in self._address_to_region_id:
                descriptor = self._address_to_region_id[absolute_address]
                # Remove this mapping
                del self._address_to_region_id[absolute_address]
                del self._region_id_to_address[descriptor.region_id]

        # Add this new region mapping
        desc = RegionDescriptor(
            region_id,
            absolute_address,
            related_function_address=related_function_address
        )

        self._address_to_region_id[absolute_address] = desc
        self._region_id_to_address[region_id] = desc

    def unmap_by_address(self, absolute_address):
        """
        Removes a mapping based on its absolute address.

        :param absolute_address: An absolute address
        """

        desc = self._address_to_region_id[absolute_address]
        del self._address_to_region_id[absolute_address]
        del self._region_id_to_address[desc.region_id]

    def absolutize(self, region_id, relative_address):
        """
        Convert a relative address in some memory region to an absolute address.

        :param region_id:           The memory region ID
        :param relative_address:    The relative memory offset in that memory region
        :return:                    An absolute address if converted, or an exception is raised when region id does not
                                    exist.
        """

        if region_id == 'global':
            # The global region always bases 0
            return relative_address

        if region_id not in self._region_id_to_address:
            raise SimRegionMapError('Non-existent region ID "%s"' % region_id)

        base_address = self._region_id_to_address[region_id].base_address
        return base_address + relative_address

    def relativize(self, absolute_address, target_region_id=None):
        """
        Convert an absolute address to the memory offset in a memory region.

        Note that if an address belongs to heap region is passed in to a stack region map, it will be converted to an
        offset included in the closest stack frame, and vice versa for passing a stack address to a heap region.
        Therefore you should only pass in address that belongs to the same category (stack or non-stack) of this region
        map.

        :param absolute_address:    An absolute memory address
        :return:                    A tuple of the closest region ID, the relative offset, and the related function
                                    address.
        """

        if target_region_id is None:
            if self.is_stack:
                # Get the base address of the stack frame it belongs to
                base_address = next(self._address_to_region_id.irange(minimum=absolute_address, reverse=False))

            else:
                try:
                    base_address = next(self._address_to_region_id.irange(maximum=absolute_address, reverse=True))

                except StopIteration:
                    # Not found. It belongs to the global region then.
                    return 'global', absolute_address, None

            descriptor = self._address_to_region_id[base_address]

        else:
            if target_region_id == 'global':
                # Just return the absolute address
                return 'global', absolute_address, None

            if target_region_id not in self._region_id_to_address:
                raise SimRegionMapError('Trying to relativize to a non-existent region "%s"' % target_region_id)

            descriptor = self._region_id_to_address[target_region_id]
            base_address = descriptor.base_address

        return descriptor.region_id, absolute_address - base_address, descriptor.related_function_address
Пример #19
0
class ProductCollection:
    def __init__(self):
        self.products = {}
        self.products_by_title = {}
        self.products_by_price = SortedDict()
        self.products_by_price_and_title = {}
        self.products_by_price_and_supplier = {}

    def add(self, product):
        self._add_to_products_by_id(product)
        self._add_to_products_by_title(product)
        self._add_to_products_by_price(product)
        self._add_to_products_by_price_and_title(product)
        self._add_to_products_by_price_and_supplier(product)

    def remove(self, _id):
        if _id not in self.products:
            return False
        product = self.products[_id]
        self._remove_from_products(product)
        self._remove_from_products_by_title(product)
        self._remove_from_products_by_price(product)
        self._remove_from_products_by_price_and_title(product)
        self._remove_from_products_by_price_and_supplier(product)

    def find_products_in_price_range(self, start_price, end_price):
        if start_price < 0 or start_price > end_price:
            raise Exception('Invalid price range!')
        prices = self.products_by_price.irange(start_price, end_price)
        return (product for price in prices
                for product in self.products_by_price[price])

    def find_products_by_title(self, title: str):
        if title not in self.products_by_title:
            return []
        return (product for product in self.products_by_title[title])

    def find_products_by_title_and_price(self, title, price):
        if title not in self.products_by_price_and_title or price not in self.products_by_price_and_title[
                title]:
            return []

        return (product
                for product in self.products_by_price_and_title[title][price])

    def find_products_by_title_and_price_range(self, title, start_price,
                                               end_price):
        if title not in self.products_by_price_and_title:
            return []
        if start_price < 0 or start_price > end_price:
            raise Exception('Invalid price range!')

        prices = self.products_by_price_and_title[title].irange(
            start_price, end_price)
        return (product for price in prices
                for product in self.products_by_price_and_title[title][price])

    def find_products_by_supplier_and_price(self, supplier, price):
        if (supplier not in self.products_by_price_and_supplier
                or price not in self.products_by_price_and_supplier[supplier]):
            return []

        return (product for product in
                self.products_by_price_and_supplier[supplier][price])

    def find_products_by_supplier_and_price_range(self, supplier, start_price,
                                                  end_price):
        if supplier not in self.products_by_price_and_supplier:
            return []
        if start_price < 0 or start_price > end_price:
            raise Exception('Invalid price range!')

        prices = self.products_by_price_and_supplier[supplier].irange(
            start_price, end_price)

        return (product for price in prices for product in
                self.products_by_price_and_supplier[supplier][price])

    def _add_to_products_by_id(self, product):
        self.products[product.id] = product

    def _remove_from_products(self, product):
        del self.products[product.id]

    def _add_to_products_by_title(self, product):
        if product.title not in self.products_by_title:
            self.products_by_title[product.title] = SortedSet()
        self.products_by_title[product.title].add(product)

    def _remove_from_products_by_title(self, product):
        self.products_by_title[product.title].remove(product)

    def _add_to_products_by_price(self, product):
        if product.price not in self.products_by_price:
            self.products_by_price[product.price] = SortedSet()

        self.products_by_price[product.price].add(product)

    def _remove_from_products_by_price(self, product):
        self.products_by_price[product.price].remove(product)

    def _add_to_products_by_price_and_title(self, product):
        if product.title not in self.products_by_price_and_title:
            self.products_by_price_and_title[product.title] = SortedDict()
        if product.price not in self.products_by_price_and_title[
                product.title]:
            self.products_by_price_and_title[product.title][
                product.price] = SortedSet()
        self.products_by_price_and_title[product.title][product.price].add(
            product)

    def _remove_from_products_by_price_and_title(self, product):
        self.products_by_price_and_title[product.title][product.price].remove(
            product)

    def _add_to_products_by_price_and_supplier(self, product):
        if product.supplier not in self.products_by_price_and_supplier:
            self.products_by_price_and_supplier[
                product.supplier] = SortedDict()
        if product.price not in self.products_by_price_and_supplier[
                product.supplier]:
            self.products_by_price_and_supplier[product.supplier][
                product.price] = SortedSet()
        self.products_by_price_and_supplier[product.supplier][
            product.price].add(product)

    def _remove_from_products_by_price_and_supplier(self, product):
        self.products_by_price_and_supplier[product.supplier][
            product.price].remove(product)
Пример #20
0
class PiecewiseConstantFunction(Generic[T]):
    def __init__(self, initial_value: float = 0) -> None:
        """ Initialize the constant function to a particular value

        :param initial_value: the starting value for the function
        """
        self.breakpoints = SortedDict()
        self._initial_value: float = initial_value

    def add_breakpoint(self,
                       xval: XValue[T],
                       yval: float,
                       squash: bool = True) -> None:
        """ Add a breakpoint to the function and update the value

        Let f(x) be the original function, and next_bp be the first breakpoint > xval; after calling
        this method, the function will be modified to f'(x) = yval for x \in [xval, next_bp)

        :param xval: the x-position of the breakpoint to add/modify
        :param yval: the value to set the function to at xval
        :param squash: if True and f(xval) = yval before calling this method, the function will remain unchanged
        """
        if squash and self.call(xval) == yval:
            return
        self.breakpoints[xval] = yval

    def add_delta(self, xval: XValue[T], delta: float) -> None:
        """ Modify the function value for x >= xval

        Let f(x) be the original function; After calling this method,
        the function will be modified to f'(x) = f(x) + delta for all x >= xval

        :param xval: the x-position of the breakpoint to add/modify
        :param delta: the amount to shift the function value by at xval
        """
        if delta == 0:
            return

        if xval not in self.breakpoints:
            self.breakpoints[xval] = self.call(xval)

        for x in self.breakpoints.irange(xval):
            self.breakpoints[x] += delta

        self.values.cache_clear()
        self.integrals.cache_clear()

    def call(self, xval: XValue[T]) -> float:
        """ Compute the output of the function at a point

        :param xval: the x-position to compute
        :returns: f(xval)
        """
        if len(self.breakpoints) == 0 or xval < self.breakpoints.keys()[0]:
            return self._initial_value
        else:
            lower_index = self.breakpoints.bisect(xval) - 1
            return self.breakpoints.values()[lower_index]

    def _breakpoint_info(
        self, index: Optional[int]
    ) -> Tuple[Optional[int], Optional[XValue[T]], float]:
        """ Helper function for computing breakpoint information

        :param index: index of the breakpoint to compute
        :returns: (index, breakpoint, value)
          * index is the breakpoint index (if it exists), or None if we're off the end
          * breakpoint is the x-value of the breakpoint, or None if we're off the end
          * value is f(breakpoint), or f(last_breakpoint) if we're off the end
        """
        try:
            breakpoint, value = self.breakpoints.peekitem(index)
        except IndexError:
            index = None
            breakpoint, value = None, self.breakpoints.values()[-1]
        return (index, breakpoint, value)

    @lru_cache(maxsize=_LRU_CACHE_SIZE
               )  # cache results of calls to this function
    def values(self, start: XValue[T], stop: XValue[T],
               step: XValueDiff[T]) -> 'SortedDict[XValue[T], float]':
        """ Compute a sequence of values of the function

        This is more efficient than [self.call(xval) for xval in range(start, stop, step)] because each self.call(..)
        takes O(log n) time due to the binary tree structure of self._breakpoints.  This method can compute the range
        of values in linear time in the range, which is significantly faster for large value ranges.

        :param start: lower bound of value sequence
        :param stop: upper bound of value sequence
        :param step: width between points in the sequence
        :returns: a SortedDict of the values of the function between start and stop, with the x-distance between
            each data-point equal to `step`; like normal "range" functions the right endpoint is not included
        """

        step = step or (stop - start)
        if len(self.breakpoints) == 0:
            num_values = int(math.ceil((stop - start) / step))
            return SortedDict([(start + step * i, self._initial_value)
                               for i in range(num_values)])

        curr_xval = start
        curr_value = self.call(start)
        next_index, next_breakpoint, next_value = self._breakpoint_info(
            self.breakpoints.bisect(start))

        sequence = SortedDict()
        while curr_xval < stop:
            sequence[curr_xval] = curr_value

            next_xval = min(stop, curr_xval + step)
            while next_breakpoint and next_xval >= next_breakpoint:
                assert next_index is not None  # if next_breakpoint is set, next_index should also be set
                curr_value = next_value
                next_index, next_breakpoint, next_value = self._breakpoint_info(
                    next_index + 1)
            curr_xval = next_xval

        return sequence

    @lru_cache(maxsize=_LRU_CACHE_SIZE
               )  # cache results of calls to this function
    def integrals(
        self,
        start: XValue[T],
        stop: XValue[T],
        step: XValueDiff[T],
        transform: Callable[[XValueDiff[T]], float] = lambda x: cast(float, x),
    ) -> 'SortedDict[XValue[T], float]':
        """ Compute a sequence of integrals of the function

        :param start: lower bound of integral sequence
        :param stop: upper bound of integral sequence
        :param step: width of each "chunk" of the integral sequence
        :param transform: function to apply to x-widths before computing the integral
        :returns: a SortedDict of the numeric integral values of the function between start and stop;
            each integral has a range of size `step`, and the key-value is the left endpoint of the chunk
        """
        step = step or (stop - start)
        if len(self.breakpoints) == 0:
            # If there are no breakpoints, just split up the range into even widths and compute
            # (width * self._initial_value) for each chunk.
            step_width = transform(step)
            range_width = transform(stop - start)
            num_full_chunks = int(range_width // step_width)
            sequence = SortedDict([(start + step * i,
                                    step_width * self._initial_value)
                                   for i in range(num_full_chunks)])

            # If the width does not evenly divide the range, compute the last chunk separately
            if range_width % step_width != 0:
                sequence[
                    start + step *
                    num_full_chunks] = range_width % step_width * self._initial_value
            return sequence

        # Set up starting loop parameters
        curr_xval = start
        curr_value = self.call(start)
        next_index, next_breakpoint, next_value = self._breakpoint_info(
            self.breakpoints.bisect(start))

        # Loop through the entire range and compute the integral of each chunk
        sequence = SortedDict()
        while curr_xval < stop:
            orig_xval = curr_xval
            next_xval = min(stop, curr_xval + step)

            # For each breakpoint in [curr_xval, next_xval), compute the area of that sub-chunk
            next_integral: float = 0
            while next_breakpoint and next_xval >= next_breakpoint:
                assert next_index is not None  # if next_breakpoint is set, next_index should also be set
                next_integral += transform(next_breakpoint -
                                           curr_xval) * curr_value
                curr_xval = next_breakpoint
                curr_value = next_value
                next_index, next_breakpoint, next_value = self._breakpoint_info(
                    next_index + 1)

            # Handle any remaining width between the last breakpoint and the end of the chunk
            next_integral += transform(next_xval - curr_xval) * curr_value
            sequence[orig_xval] = next_integral

            curr_xval = next_xval

        return sequence

    def integral(
        self,
        start: XValue[T],
        stop: XValue[T],
        transform: Callable[[XValueDiff[T]], float] = lambda x: cast(float, x),
    ) -> float:
        """ Helper function to compute the integral of the whole specified range

        :param start: lower bound of the integral
        :param stop: upper bound of the integral
        :returns: the integral of the function between start and stop
        """
        return self.integrals(start, stop, (stop - start),
                              transform).values()[0]

    def __str__(self) -> str:
        ret = f'{self._initial_value}, x < {self.breakpoints.keys()[0]}\n'
        for xval, yval in self.breakpoints.items():
            ret += f'{yval}, x >= {xval}\n'
        return ret

    def __add__(
        self, other: 'PiecewiseConstantFunction[T]'
    ) -> 'PiecewiseConstantFunction[T]':
        new_func: 'PiecewiseConstantFunction[T]' = PiecewiseConstantFunction(
            self._initial_value + other._initial_value)
        for xval, y0, y1 in _merged_breakpoints(self, other):
            new_func.add_breakpoint(xval, y0 + y1)
        return new_func

    def __sub__(
        self, other: 'PiecewiseConstantFunction[T]'
    ) -> 'PiecewiseConstantFunction[T]':
        new_func: 'PiecewiseConstantFunction[T]' = PiecewiseConstantFunction(
            self._initial_value - other._initial_value)
        for xval, y0, y1 in _merged_breakpoints(self, other):
            new_func.add_breakpoint(xval, y0 - y1)
        return new_func

    def __mul__(
        self, other: 'PiecewiseConstantFunction[T]'
    ) -> 'PiecewiseConstantFunction[T]':
        new_func: 'PiecewiseConstantFunction[T]' = PiecewiseConstantFunction(
            self._initial_value * other._initial_value)
        for xval, y0, y1 in _merged_breakpoints(self, other):
            new_func.add_breakpoint(xval, y0 * y1)
        return new_func

    def __truediv__(
        self, other: 'PiecewiseConstantFunction[T]'
    ) -> 'PiecewiseConstantFunction[T]':
        try:
            new_func: 'PiecewiseConstantFunction[T]' = PiecewiseConstantFunction(
                self._initial_value / other._initial_value)
        except ZeroDivisionError:
            new_func = PiecewiseConstantFunction()

        for xval, y0, y1 in _merged_breakpoints(self, other):
            try:
                new_func.add_breakpoint(xval, y0 / y1)
            except ZeroDivisionError:
                new_func.add_breakpoint(xval, 0)
        return new_func
Пример #21
0
class DwarfData(object):
    """
    Loads dwarf data from the given file.

    Data:
        - DIEs: Apart from attributes, it also contains DIE offset as well as
                parent DIE offset in order to build a tree. 
        - LineInfoMap: Instruction address to LineInfoEntry
        - LineInfoEntries: Map of entries containing line info

    TODO: - Include DW_TAG_formal_parameter?
    """
    def __init__(self, path):
        self._dwData = None
        self._dieTree = None
        self._subDies = None
        self._subprograms = None
        self._inlSubroutines = {}
        self._dwLineMap = None

        # Read dwarf data.
        self._read_data(path)
        assert self._validate_data(), "Invalid dwarf data."

        # Process dwarf data.
        self._build_die_tree()
        self._find_subprograms()
        self._find_inlined_subroutines()

        # Save LineInfoMap as SortedDict
        self._dwLineMap = SortedDict({
            int(k): int(v)
            for k, v in self._dwData['LineInfoMap'].iteritems()
        })

    def get_dw_lines(self, ranges):
        """
        Return dwarf lines that belong to given instruction address ranges
        Args
            ranges : List of ranges to retrieve dwarf line entries for

        Return
            dwLines : Dict of DwarfLine entries referenced in given range list
        """
        dwLineIndices = []
        for r in ranges:
            for dwl_i in self._dwLineMap.irange(minimum=r[0], maximum=r[1]):
                dwLineIndices.append(self._dwLineMap[dwl_i])

        dwLines = {
            i: self._dwData['LineInfoEntries'][str(i)]
            for i in set(dwLineIndices)
        }
        return dwLines

    def get_line_info(self, ranges):
        """
        Returns line info for the given address ranges.

        Args:
            ranges : list of address ranges

        Return:
            Returns dict containing the following elements:
                - begin : Begin Line
                - end   : End Line
                - max   : Min Line
                - min   : Max Line
            
            where each element is a dict: {l, c, d} where:
                - l : line
                - c : column
                - d : discriminator
            
            * All values are integers.
        """
        assert isinstance(ranges, list)

        l_info = {'begin': {}, 'end': {}, 'max': {}, 'min': {}}

        def get_lcd(dwlIndex):
            dwl = self._dwData['LineInfoEntries'].get(str(dwlIndex), None)
            if dwl is None:
                assert False, "Invalid dwLine index."
            return {
                'l': int(dwl['LineNumber']),
                'c': int(dwl['LineOffset']),
                'd': int(dwl['Discriminator'])
            }

        l_info['begin'] = get_lcd(self._dwLineMap[ranges[0][0]])
        l_info['end'] = get_lcd(self._dwLineMap[ranges[-1][-1]])

        minLine = None
        maxLine = None
        for r in ranges:
            for dwl_i in self._dwLineMap.irange(minimum=r[0], maximum=r[1]):
                dwlIndex = self._dwLineMap[dwl_i]
                lcd = get_lcd(dwlIndex)
                if minLine is None and maxLine is None:
                    minLine = lcd
                    maxLine = lcd
                    continue

                # Max
                if lcd['l'] > maxLine['l']:
                    maxLine = lcd
                elif lcd['l'] == maxLine['l']:
                    if lcd['c'] > maxLine['c']:
                        maxLine = lcd
                    elif lcd['c'] == maxLine['c']:
                        if lcd['d'] > maxLine['d']:
                            maxLine = lcd

                # Same for min...
                if lcd['l'] < minLine['l']:
                    minLine = lcd
                elif lcd['l'] == minLine['l']:
                    if lcd['c'] < minLine['c']:
                        minLine = lcd
                    elif lcd['c'] == minLine['c']:
                        if lcd['d'] < minLine['d']:
                            minLine = lcd

        # Update min/max
        l_info['max'] = maxLine
        l_info['min'] = minLine

        return l_info

    def get_subprograms(self):
        """
        Get subprogram dict.

        Return:
            Dict keyed by subprogram low_pc (serving as entry point) with values
            of the following form: {name, dieOffset}.
        """
        return self._subprograms

    def get_subprogram_file(self, dieOffset):
        """
        Returns the file associated with the given subprogram DIE.

        Args:
            dieOffset: Offset of subprogram DIE.

        Return:
            Tuple (comp_dir, filename).

        FIXME:
            Return the actual file. The source file table must be exported in JSON 
            (it is already parsed from the elf object). For now, the file path
            of parent CU is returned instead.
        """
        assert dieOffset in self._subDies, "Invalid subprogram DIE offset."

        # Get parent DIE (CU)
        cuDie = list(self._dieTree.predecessors(dieOffset))
        assert len(cuDie) == 1, "Invalid DIE tree."
        cuDie = cuDie[0]

        # Get the source file table associated with this CU. Use DW_AT_decl_file
        # index for retrieving the actual file.

        # FIXME: Return the actual file.
        cuDieAttrs = self._dieTree.nodes[cuDie]['attrs']
        return cuDieAttrs['DW_AT_comp_dir'], cuDieAttrs['DW_AT_name']

    def get_inlined_subroutines(self, subDieOffset):
        """
        Returns a list of inlined subroutines for the given subprogram DIE offset.

        Args:
            subDieOffset: Valid subprogram DIE offset.

        Note:
          ** high_pc points at first byte after last instruction

        Return:
            List of inlined subroutines of the form {dieOffset, low_pc, high_pc}.
            Returns an empty list if given subprogram does not contain inlined
            subroutines.
        """
        assert subDieOffset in self._subDies
        return self._inlSubroutines.get(subDieOffset, [])

    def get_local_variables(self, subDieOffset):
        """
        Returns local variables given a subroutine DIE offset. The subroutine DIE
        may be a normal subroutine or an abstract instance root entry (part of 
        self._subDies).
        
        Note:
            SP register hardcoded for avr (fbreg28).
        
        Args:
            subDieOffset: Offset of subprogram DIE containing variables.
        
        Return:
            A dict keyed by variable offset in stack, with values of the form
            {name, byteSize}.
        """
        def get_locs(chDie):
            rx = re.compile(r'\[([^\]]+)\];')
            return rx.findall(chDie['attrs']['DW_AT_location'])

        def get_ops(loc):
            assert loc[-1] == ','
            return loc.split(',')[:-1]

        assert subDieOffset in self._subDies, \
            "Invalid subprogram DIE offset."

        localVars = {}

        for s in self._dieTree.successors(subDieOffset):
            chDie = self._dieTree.nodes[s]
            if chDie['tag'] != 'DW_TAG_variable':
                continue
            if not {'DW_AT_name', 'DW_AT_location', 'DW_AT_type'} <= set(
                    chDie['attrs'].keys()):
                continue

            locs = get_locs(chDie)
            name = chDie['attrs']['DW_AT_name']

            if len(locs) == 1:
                ops = get_ops(locs[0])
                if len(ops) == 1:
                    o = ops[0].split(':')
                    if len(o) == 2:
                        k = o[0]
                        v = o[1]
                    else:
                        continue

                    if k != 'DW_OP_breg28':
                        continue

                    # Get base type
                    typeDieOffset = int(chDie['attrs']['DW_AT_type'])
                    if not self._dieTree.has_node(typeDieOffset):
                        continue
                    bs = self._get_type_byte_size(typeDieOffset)
                    localVars[v] = {'name': name, 'byteSize': bs}

        return localVars

    def _get_type_byte_size(self, die):
        """Decodes byte size for a DIE with tag DW_TAG_base_type."""
        if not self._dieTree.has_node(die):
            assert False, "Invalid DIE offset."

        die = self._dieTree.nodes[die]
        assert die['tag'] == 'DW_TAG_base_type'
        assert 'DW_AT_byte_size' in die['attrs'].keys()

        return self._decode_dwarf_constant(die['attrs']['DW_AT_byte_size'])[1]

    def _decode_dwarf_constant(self, attr):
        """
        Decodes a dwarf constant of the form S_{}_U_{}, returning signed, unsigned.
        
        Args:
            attr: Attribute (string) of the form S_{}_U_{}.

        Return:
            Tuple (s, u), signed and unsigned respectively.
        """
        m = re.search(r'S_([+\-0-9]+)_U_([+\-0-9]+)', attr)
        assert m is not None

        s = int(m.group(1))
        u = int(m.group(2))
        assert u >= 0
        return s, u

    def _find_inlined_subroutines(self):
        """
        Scans subtrees with roots in self._subDies for DIEs with tag 
        'DW_AT_inlined_subroutine'. Only inlined subroutines which
        have low_pc, high_pc are processed here. Nested inlining is not
        supported.
        
        Note:
            Inlined subroutine DIEs are marked in self._inlSubroutines. This
            dict is keyed by subprogram DIE offset, where each value is a list 
            of inlined subroutines of the form {dieOffset, low_pc, high_pc}.
        
        Return:
            Void
        """
        def add_inl(die, node, subp):
            """
            Add the inlined subroutine to self._inlSubroutines. 'Decode' high_pc
            if it represents an offset.
            
            Args:
                die:  Die offset.
                node: Die node is self._dieTree.
                subp: Offset of subprogram DIE containing inlined node.
            
            Return:
                Void.
            """
            assert subp in self._subDies

            # Get range
            lo_pc = int(node['attrs']['DW_AT_low_pc'])

            if node['attrs']['DW_AT_high_pc'][0] == 'S':
                # Decode dwarf constant
                _, off = self._decode_dwarf_constant(
                    node['attrs']['DW_AT_high_pc'])
                hi_pc = lo_pc + off
            else:
                hi_pc = int(node['attrs']['DW_AT_high_pc'])

            # Update self._inlSubroutines
            if subp not in self._inlSubroutines:
                self._inlSubroutines[subp] = []

            self._inlSubroutines[subp].append({
                'dieOffset': die,
                'low_pc': lo_pc,
                'high_pc': hi_pc
            })

        def get_surrounding_sub(dnode):
            """Walk up the tree and return next surrounding subroutine
            Not necessarily the direct parent. There may be lexical scopes and other stuff.
            """
            n = dnode
            try:
                while n:
                    n = next(iter(self._dieTree.predecessors(n)))
                    dd = self._dieTree.nodes[n]
                    if 'tag' in dd and dd['tag'] in SUBROUTINE:
                        return n
            except StopIteration:
                pass
            return None

        def get_sub_details(doff):
            """Return dict with details of subroutine. If die is inlined, go to abstract origin"""
            assert int(doff) in self._dieTree.nodes, "DWARF data incomplete"
            # --
            dd = self._dieTree.nodes[int(doff)]
            assert dd['tag'] in SUBROUTINE, "not a subprogram"
            if dd['tag'] == "DW_TAG_inlined_subroutine":
                return get_sub_details(dd['attrs']['DW_AT_abstract_origin'])
            return dd['attrs']

        def test_die(die, subp):
            """
            Test if die is an inlined node, add it to self._inlSubroutines.

            Note:
                - Nested inlining not supported.
                - DW_AT_ranges not supported.

            Args:
                die:  Die offset.
                subp: Subprogram die offset.

            Returns:
                Void.
            """
            dNode = self._dieTree.nodes[die]
            if dNode['tag'] == 'DW_TAG_inlined_subroutine':
                inlined_into = int(get_surrounding_sub(die))
                abstract_orig = int(dNode['attrs']['DW_AT_abstract_origin'])
                assert inlined_into is not None, "Inlined sub without surrounding sub"
                sub_into = get_sub_details(inlined_into)
                sub_myself = get_sub_details(abstract_orig)
                log.info(
                    "Sub '{}' (die +{:x}) was inlined into '{}' (die +{:x})".
                    format(sub_myself['DW_AT_name'], die,
                           sub_into['DW_AT_name'], inlined_into))

                if self._dieTree.node[inlined_into][
                        'tag'] == 'DW_TAG_inlined_subroutine':
                    raise NotImplementedError(
                        "nested inlining @die offset {:x}".format(die))

                # Check if it contains DW_AT_ranges
                if 'DW_AT_ranges' in dNode['attrs']:
                    raise NotImplementedError(
                        "Inlined subroutine not in contiguous range.")

                assert {'DW_AT_low_pc', 'DW_AT_high_pc'} <= set(dNode['attrs'].keys()), \
                    "Incorrect inlined subroutine DIE @0d{}".format(die)

                # Add inlined subroutine
                add_inl(die, dNode, subp)

        def find_children(subt, root):
            """Search the subtree rooted by subp recursively."""
            for ch in self._dieTree.successors(subt):
                test_die(ch, root)
                find_children(ch, root)

        for s in self._subDies:
            find_children(s, s)
        if self._inlSubroutines:
            log.warning("Support for inlining is experimental!")

    def _find_subprograms(self):
        """
        Finds subprograms that have debug info, notes them in self._subprograms.

        
        NOTE: Only subprogram DIEs that are direct children of CU DIEs are
              processed here (as it should be).

        NOTE: Only subprogram DIEs that have low_pc, high_pc are processed here.
              DIEs with an DW_AT_ranges are skipped here (non-contiguous case).
              To be completed later... (FIXME).

           ** It is assumed that low_pc represents the entry point of the 
              subprogram. If this is not the case, then the Executable class will
              simply assume that this subroutine does not possess debug info 
              (mismatch between entry point as symbol address in json data and
               the low_pc address here).
        -----

        NOTE: Subprogram DIEs that are abstract instance root entries are 
              are not added to self._subprograms, but are noted in self._subDies.
              This happens for subprograms that are always inlined, so that there
              is no external symbol left.
        
        NOTE: It can happen that a subprogram DIE (DW_TAG_subprogram) which is
              not a concrete out-of-line instance (see below) may be missing 
              a DW_AT_name or DW_AT_low_pc attribute. Example (memmove in maxleaf).
        -----

        NOTE: DIEs with DW_TAG_subprogram which have an DW_AT_abstract_origin
              attribute represent a concrete out-of-line instance of an
              inlinable subroutine.
        """
        self._subDies = []
        self._subprograms = {}

        # Walk the tree, only traverse CU's that have DW_AT_comp_dir and
        # DW_AT_name attributes.
        #
        # FIXME: Process all CU's after fixing self.get_subprogram_file()
        cuDies = self._dieTree.successors(0)
        for cu in cuDies:
            cuAttrs = self._dieTree.nodes[cu]['attrs']
            if not {'DW_AT_name', 'DW_AT_comp_dir'} <= set(cuAttrs.keys()):
                continue

            # Find subprogram DIE's that are children of current CU DIE
            for s in self._dieTree.successors(cu):
                die = self._dieTree.nodes[s]

                if die['tag'] != 'DW_TAG_subprogram':
                    continue

                # Check if it has an abstract origin attribute -> concrete
                # out of line instance.
                if 'DW_AT_abstract_origin' in die['attrs']:
                    # log.debug("Skipping subprog DIE @0d{}, concrete out-of-line
                    # instance".format(s))
                    continue

                # Don't process abstract instance root entries here but note
                # them in self._subDies.
                if 'DW_AT_inline' in die['attrs']:
                    log.debug(
                        "Subprog DIE @0d{} is an abstract instance root entry."
                        .format(s))
                    self._subDies.append(s)
                    continue

                if not {'DW_AT_low_pc'} <= set(die['attrs'].keys()):
                    # a declaration
                    continue

                if 'DW_AT_ranges' in die['attrs']:
                    log.error(
                        "Skipping subprogram DIE @0d{}, contains DW_AT_ranges."
                        .format(s))
                    continue

                self._subprograms.update({
                    int(die['attrs']['DW_AT_low_pc']): {
                        'name': die['attrs']['DW_AT_name'],
                        'dieOffset': s
                    }
                })
                self._subDies.append(s)

    def _build_die_tree(self):
        """
        Builds the die tree as nx.DiGraph. Children of the root node are 
        Compilation Unit DIE's found in .debug_info section. Each node has
        the following attributes:
            - tag   : DW_TAG_*
            - attrs : DW_AT_*

        Note:
            Nodes are keyed by DIE offset, the root node having offset 0.
        """

        dieTree = nx.DiGraph()
        dieTree.add_node(0, tag='root')

        for die in self._dwData['DIEs']:
            assert die['Offset'] != 0, "Invalid DIE."
            # Add node
            dieTree.add_node(die['Offset'],
                             tag=die['Tag'],
                             attrs=die['Attributes'])
            # Add edge to parent
            dieTree.add_edge(die['ParentOffset'], die['Offset'])

        # Check if graph is indeed a tree, raise an error if not.
        assert nx.is_tree(dieTree), "Invalid DIE tree."

        self._dieTree = dieTree

    def _read_data(self, path):
        with open(path, 'r') as fp:
            self._dwData = json.load(fp)

    def _validate_data(self):
        status = False
        while True:
            if not {'Type', 'Data'} <= set(self._dwData.keys()):
                break
            if self._dwData['Type'] != 'DebugInfo':
                break
            if not {'DIEs', 'LineInfoEntries', 'LineInfoMap'} <= \
                    set(self._dwData['Data'].keys()):
                break
            if len(self._dwData['Data']['DIEs']) < 1:
                log.error("Empty DIE map.")
                break

            self._dwData = self._dwData['Data']
            status = True
            break
        return status
Пример #22
0
class DynamicCHConfig(MemcacheKVConfiguration):
    def __init__(self, cache_nodes, db_node, write_mode, c, hash_space):
        super().__init__(cache_nodes, db_node, write_mode)
        self.c = c
        self.hash_space = hash_space
        self.key_hash_ring = SortedDict()
        self.node_hash_ring = SortedDict()
        self.node_hashes = {}
        self.key_rates = {}
        self.iloads = {}
        self.ploads = {}
        for node in self.cache_nodes:
            self.iloads[node.id] = 0
            self.ploads[node.id] = 0
            node_hash = node.id * (hash_space // len(cache_nodes))
            self.node_hashes[node.id] = node_hash
            self.node_hash_ring[node_hash] = node.id

    def key_hash_fn(self, key):
        return hash(key)

    def lookup_node(self, key):
        key_hash = self.key_hash_fn(key) % self.hash_space
        for (node_hash, node_id) in self.node_hash_ring.items():
            if node_hash >= key_hash:
                return node_id
        return self.node_hash_ring.peekitem(0)[1]

    def install_key(self, key):
        key_hash = self.key_hash_fn(key) % self.hash_space
        keys = self.key_hash_ring.setdefault(key_hash, set())
        keys.add(key)

    def remove_key(self, key):
        key_hash = self.key_hash_fn(key) % self.hash_space
        keys = self.key_hash_ring.get(key_hash, set())
        keys.discard(key)

    def search_migration_keys(self, node_id, starting_hash, agg_pload,
                              target_pload, migration_keys):
        mapped_key_hashes = list(
            self.key_hash_ring.irange(minimum=0,
                                      maximum=starting_hash,
                                      reverse=True))
        new_node_hash = None
        for key_hash in mapped_key_hashes:
            if key_hash in self.node_hash_ring:
                assert self.node_hash_ring[key_hash] == node_id

            prev_hash = (key_hash - 1) % self.hash_space
            if prev_hash in self.node_hash_ring:
                # Never collide two nodes on the hash ring!
                assert self.node_hash_ring[prev_hash] != node_id
                new_node_hash = key_hash
                break

            for key in self.key_hash_ring[key_hash]:
                agg_pload += self.key_rates[key].rate()
                if agg_pload >= target_pload:
                    new_node_hash = prev_hash
                migration_keys.append(key)
            if new_node_hash is not None:
                break
        return (agg_pload, new_node_hash)

    def rehash_node(self, node_id, target_pload):
        node_hash = self.node_hashes[node_id]
        migration_dst = None
        # Find next node in the hash ring to migrate to
        for (next_node_hash, next_node_id) in self.node_hash_ring.items():
            if next_node_hash > node_hash:
                migration_dst = next_node_id
                break
        if migration_dst is None:
            migration_dst = self.node_hash_ring.peekitem(0)[1]
        assert migration_dst != node_id

        # Find set of keys to migrate, and the new node hash
        migration_keys = []
        (agg_pload,
         new_node_hash) = self.search_migration_keys(node_id, node_hash, 0,
                                                     target_pload,
                                                     migration_keys)
        if new_node_hash is None:
            # Search beyond hash 0
            (agg_pload, new_node_hash) = self.search_migration_keys(
                node_id, self.hash_space - 1, agg_pload, target_pload,
                migration_keys)
            assert new_node_hash is not None

        if new_node_hash == node_hash:
            # We cannot find a new hash. This is due to
            # the next hash on the ring already occupied
            # by another node.
            assert len(migration_keys) == 0
            return None

        # Update node hash
        self.node_hashes[node_id] = new_node_hash
        self.node_hash_ring.pop(node_hash)
        self.node_hash_ring[new_node_hash] = node_id

        # Update pload
        self.ploads[node_id] -= agg_pload
        self.ploads[migration_dst] += agg_pload

        return [
            MemcacheKVRequest.MigrationRequest(
                keys=migration_keys, dst=self.cache_nodes[migration_dst])
        ]

    def key_to_nodes(self, key, op_type):
        if op_type == kv.Operation.Type.PUT or op_type == kv.Operation.Type.DEL:
            if op_type == kv.Operation.Type.PUT:
                self.install_key(key)
            elif op_type == kv.Operation.Type.DEL:
                self.remove_key(key)
            node_id = self.lookup_node(key)
            return MappedNodes([self.cache_nodes[node_id]], None)
        else:
            node_id = self.lookup_node(key)
            total_iload = sum(self.iloads.values())
            expected_iload = (self.c * total_iload) / len(self.cache_nodes)
            total_pload = sum(self.ploads.values())
            expected_pload = (self.c * total_pload) / len(self.cache_nodes)

            if self.iloads[node_id] <= expected_iload or self.ploads[
                    node_id] <= expected_pload:
                return MappedNodes([self.cache_nodes[node_id]], None)

            pload_diff = self.ploads[node_id] - expected_pload
            migration_requests = self.rehash_node(node_id, pload_diff)
            return MappedNodes([self.cache_nodes[node_id]], migration_requests)

    def report_op_send(self, node, op, time):
        self.iloads[node.id] += 1
        node_id = self.lookup_node(op.key)
        key_rate = self.key_rates.setdefault(op.key, KeyRate())
        old_rate = key_rate.rate()
        key_rate.count += 1
        key_rate.time = time
        self.ploads[node_id] += (key_rate.rate() - old_rate)

    def report_op_receive(self, node):
        self.iloads[node.id] -= 1
Пример #23
0
class RegionMap(object):
    """
    Mostly used in SimAbstractMemory, RegionMap stores a series of mappings between concrete memory address ranges and
    memory regions, like stack frames and heap regions.
    """
    def __init__(self, is_stack):
        """
        Constructor

        :param is_stack:    Whether this is a region map for stack frames or not. Different strategies apply for stack
                            regions.
        """
        self.is_stack = is_stack

        # A sorted list, which maps stack addresses to region IDs
        self._address_to_region_id = SortedDict()
        # A dict, which maps region IDs to memory address ranges
        self._region_id_to_address = {}

    #
    # Properties
    #

    def __repr__(self):
        return "RegionMap<%s>" % ("S" if self.is_stack else "H")

    @property
    def is_empty(self):
        return len(self._address_to_region_id) == 0

    @property
    def stack_base(self):
        if not self.is_stack:
            raise SimRegionMapError(
                'Calling "stack_base" on a non-stack region map.')

        return next(self._address_to_region_id.irange(reverse=True))

    @property
    def region_ids(self):
        return self._region_id_to_address.keys()

    #
    # Public methods
    #

    @SimStatePlugin.memo
    def copy(self, memo):  # pylint: disable=unused-argument
        r = RegionMap(is_stack=self.is_stack)

        # A shallow copy should be enough, since we never modify any RegionDescriptor object in-place
        r._address_to_region_id = self._address_to_region_id.copy()
        r._region_id_to_address = self._region_id_to_address.copy()

        return r

    def map(self, absolute_address, region_id, related_function_address=None):
        """
        Add a mapping between an absolute address and a region ID. If this is a stack region map, all stack regions
        beyond (lower than) this newly added regions will be discarded.

        :param absolute_address:            An absolute memory address.
        :param region_id:                   ID of the memory region.
        :param related_function_address:    A related function address, mostly used for stack regions.
        """

        if self.is_stack:
            # Sanity check
            if not region_id.startswith('stack_'):
                raise SimRegionMapError(
                    'Received a non-stack memory ID "%d" in a stack region map'
                    % region_id)

            # Remove all stack regions that are lower than the one to add
            while True:
                try:
                    addr = next(
                        self._address_to_region_id.irange(
                            maximum=absolute_address, reverse=True))
                    descriptor = self._address_to_region_id[addr]
                    # Remove this mapping
                    del self._address_to_region_id[addr]
                    # Remove this region ID from the other mapping
                    del self._region_id_to_address[descriptor.region_id]
                except StopIteration:
                    break

        else:
            if absolute_address in self._address_to_region_id:
                descriptor = self._address_to_region_id[absolute_address]
                # Remove this mapping
                del self._address_to_region_id[absolute_address]
                del self._region_id_to_address[descriptor.region_id]

        # Add this new region mapping
        desc = RegionDescriptor(
            region_id,
            absolute_address,
            related_function_address=related_function_address)

        self._address_to_region_id[absolute_address] = desc
        self._region_id_to_address[region_id] = desc

    def unmap_by_address(self, absolute_address):
        """
        Removes a mapping based on its absolute address.

        :param absolute_address: An absolute address
        """

        desc = self._address_to_region_id[absolute_address]
        del self._address_to_region_id[absolute_address]
        del self._region_id_to_address[desc.region_id]

    def absolutize(self, region_id, relative_address):
        """
        Convert a relative address in some memory region to an absolute address.

        :param region_id:           The memory region ID
        :param relative_address:    The relative memory offset in that memory region
        :return:                    An absolute address if converted, or an exception is raised when region id does not
                                    exist.
        """

        if region_id == 'global':
            # The global region always bases 0
            return relative_address

        if region_id not in self._region_id_to_address:
            raise SimRegionMapError('Non-existent region ID "%s"' % region_id)

        base_address = self._region_id_to_address[region_id].base_address
        return base_address + relative_address

    def relativize(self, absolute_address, target_region_id=None):
        """
        Convert an absolute address to the memory offset in a memory region.

        Note that if an address belongs to heap region is passed in to a stack region map, it will be converted to an
        offset included in the closest stack frame, and vice versa for passing a stack address to a heap region.
        Therefore you should only pass in address that belongs to the same category (stack or non-stack) of this region
        map.

        :param absolute_address:    An absolute memory address
        :return:                    A tuple of the closest region ID, the relative offset, and the related function
                                    address.
        """

        if target_region_id is None:
            if self.is_stack:
                # Get the base address of the stack frame it belongs to
                base_address = next(
                    self._address_to_region_id.irange(minimum=absolute_address,
                                                      reverse=False))

            else:
                try:
                    base_address = next(
                        self._address_to_region_id.irange(
                            maximum=absolute_address, reverse=True))

                except StopIteration:
                    # Not found. It belongs to the global region then.
                    return 'global', absolute_address, None

            descriptor = self._address_to_region_id[base_address]

        else:
            if target_region_id == 'global':
                # Just return the absolute address
                return 'global', absolute_address, None

            if target_region_id not in self._region_id_to_address:
                raise SimRegionMapError(
                    'Trying to relativize to a non-existent region "%s"' %
                    target_region_id)

            descriptor = self._region_id_to_address[target_region_id]
            base_address = descriptor.base_address

        return descriptor.region_id, absolute_address - base_address, descriptor.related_function_address
Пример #24
0
class CFBlanket(Analysis):
    """
    A Control-Flow Blanket is a representation for storing all instructions, data entries, and bytes of a full program.
    """
    def __init__(self, cfg=None):
        self._blanket = SortedDict()

        self._ffi = cffi.FFI()

        if cfg is not None:
            self._from_cfg(cfg)
        else:
            _l.debug("CFG is not specified. Initialize CFBlanket from the knowledge base.")
            for func in self.kb.functions.values():
                self.add_function(func)

    def floor_addr(self, addr):
        try:
            return next(self._blanket.irange(maximum=addr, reverse=True))
        except StopIteration:
            raise KeyError(addr)

    def floor_item(self, addr):
        key = self.floor_addr(addr)
        return key, self._blanket[key]

    def floor_items(self, addr=None):
        if addr is None:
            start_addr = None
        else:
            try:
                start_addr = next(self._blanket.irange(maximum=addr, reverse=True))
            except StopIteration:
                start_addr = addr

        for key in self._blanket.irange(minimum=start_addr):
            yield key, self._blanket[key]

    def ceiling_addr(self, addr):
        try:
            return next(self._blanket.irange(minimum=addr, reverse=False))
        except StopIteration:
            raise KeyError(addr)

    def ceiling_item(self, addr):
        key = self.ceiling_addr(addr)
        return key, self._blanket[key]

    def __getitem__(self, addr):
        return self._blanket[addr]

    def add_obj(self, addr, obj):
        """
        Adds an object `obj` to the blanket at the specified address `addr`
        """
        self._blanket[addr] = obj

    def add_function(self, func):
        """
        Add a function `func` and all blocks of this function to the blanket.
        """
        for block in func.blocks:
            self.add_obj(block.addr, block)

    def dbg_repr(self):
        """
        The debugging representation of this CFBlanket.

        :return:    The debugging representation of this CFBlanket.
        :rtype:     str
        """

        output = [ ]

        for obj in self.project.loader.all_objects:
            for section in obj.sections:
                if section.memsize == 0:
                    continue
                min_addr, max_addr = section.min_addr, section.max_addr
                output.append("### Object %s" % repr(section))
                output.append("### Range %#x-%#x" % (min_addr, max_addr))

                pos = min_addr
                while pos < max_addr:
                    try:
                        addr, thing = self.floor_item(pos)
                        output.append("%#x: %s" % (addr, repr(thing)))

                        if thing.size == 0: pos += 1
                        else: pos += thing.size
                    except KeyError:
                        pos += 1

                output.append("")

        return "\n".join(output)

    def _from_cfg(self, cfg):
        """
        Initialize CFBlanket from a CFG instance.

        :param cfg: A CFG instance.
        :return:    None
        """

        # Let's first add all functions first
        for func in cfg.kb.functions.values():
            self.add_function(func)

        self._mark_unknowns()

    def _mark_unknowns(self):
        """
        Mark all unmapped regions.

        :return: None
        """

        for obj in self.project.loader.all_objects:
            if isinstance(obj, cle.ELF):
                # sections?
                if obj.sections:
                    for section in obj.sections:
                        if not section.memsize or not section.vaddr:
                            continue
                        min_addr, max_addr = section.min_addr, section.max_addr
                        self._mark_unknowns_core(min_addr, max_addr, obj=obj, section=section)
                elif obj.segments:
                    for segment in obj.segments:
                        if not segment.memsize:
                            continue
                        min_addr, max_addr = segment.min_addr, segment.max_addr
                        self._mark_unknowns_core(min_addr, max_addr, obj=obj, segment=segment)
                else:
                    # is it empty?
                    _l.warning("Empty ELF object %s.", repr(obj))
            elif isinstance(obj, cle.PE):
                if obj.sections:
                    for section in obj.sections:
                        if not section.memsize:
                            continue
                        min_addr, max_addr = section.min_addr, section.max_addr
                        self._mark_unknowns_core(min_addr, max_addr, obj=obj, section=section)
                else:
                    # is it empty?
                    _l.warning("Empty PE object %s.", repr(obj))
            else:
                min_addr, max_addr = obj.min_addr, obj.max_addr
                self._mark_unknowns_core(min_addr, max_addr, obj=obj)

    def _mark_unknowns_core(self, min_addr, max_addr, obj=None, segment=None, section=None):

        try:
            addr = self.floor_addr(min_addr)
            if addr < min_addr:
                raise KeyError
        except KeyError:
            # there is no other lower address
            try:
                next_addr = self.ceiling_addr(min_addr)
                if next_addr >= max_addr:
                    raise KeyError
            except KeyError:
                next_addr = max_addr

            size = next_addr - min_addr
            if obj is None or isinstance(obj, cle.ExternObject):
                bytes_ = None
            else:
                try:
                    _l.debug("Loading bytes from object %s, section %s, segmeng %s, addresss %#x.",
                             obj, section, segment, min_addr)
                    bytes_ = self.project.loader.memory.load(min_addr, size)
                except KeyError:
                    # The address does not exist
                    bytes_ = None
            self.add_obj(min_addr,
                         Unknown(min_addr, size, bytes_=bytes_, object_=obj, segment=segment, section=section)
                         )

        addr = min_addr
        while addr < max_addr:
            last_addr, last_item = self.floor_item(addr)
            if last_addr < min_addr:
                # impossible
                raise Exception('Impossible')

            if last_item.size == 0:
                # Make sure everything has a non-zero size
                last_item_size = 1
            else:
                last_item_size = last_item.size
            end_addr = last_addr + last_item_size
            if end_addr < max_addr:
                try:
                    next_addr = self.ceiling_addr(end_addr)
                except KeyError:
                    next_addr = max_addr
                if next_addr > end_addr:
                    # there is a gap
                    size = next_addr - end_addr
                    if obj is None or isinstance(obj, cle.ExternObject):
                        bytes_ = None
                    else:
                        try:
                            _l.debug("Loading bytes from object %s, section %s, segmeng %s, addresss %#x.",
                                     obj, section, segment, next_addr)
                            bytes_ = self.project.loader.memory.load(next_addr, size)
                        except KeyError:
                            # The address does not exist
                            bytes_ = None
                    self.add_obj(end_addr,
                                 Unknown(end_addr, size, bytes_=bytes_, object_=obj, segment=segment, section=section)
                                 )
                addr = next_addr
            else:
                addr = max_addr
Пример #25
0
class KeyedRegion(object):
    """
    KeyedRegion keeps a mapping between stack offsets and all variables covering that offset. It assumes no variable in
    this region overlap with another variable in this region.

    Registers and function frames can all be viewed as a keyed region.
    """
    def __init__(self, tree=None):
        self._storage = SortedDict() if tree is None else tree

    def _get_container(self, offset):
        try:
            base_offset = next(
                self._storage.irange(maximum=offset, reverse=True))
        except StopIteration:
            return offset, None
        else:
            container = self._storage[base_offset]
            if container.includes(offset):
                return base_offset, container
            return offset, None

    def __contains__(self, offset):
        """
        Test if there is at least one varaible covering the given offset.

        :param offset:
        :return:
        """

        return self._get_container(offset)[1] is not None

    def __len__(self):
        return len(self._storage)

    def __iter__(self):
        return self._storage.itervalues()

    def __eq__(self, other):
        if set(self._storage.keys()) != set(other._storage.keys()):
            return False

        for k, v in self._storage.iteritems():
            if v != other._storage[k]:
                return False

        return True

    def copy(self):
        if not self._storage:
            return KeyedRegion()

        kr = KeyedRegion()
        for key, ro in self._storage.iteritems():
            kr._storage[key] = ro.copy()
        return kr

    def merge(self, other, make_phi_func=None):
        """
        Merge another KeyedRegion into this KeyedRegion.

        :param KeyedRegion other: The other instance to merge with.
        :return: None
        """

        # TODO: is the current solution not optimal enough?
        for _, item in other._storage.iteritems():  # type: RegionObject
            for loc_and_var in item.objects:
                self.__store(loc_and_var,
                             overwrite=False,
                             make_phi_func=make_phi_func)

        return self

    def dbg_repr(self):
        """
        Get a debugging representation of this keyed region.
        :return: A string of debugging output.
        """
        keys = self._storage.keys()
        offset_to_vars = {}

        for key in sorted(keys):
            ro = self._storage[key]
            variables = [obj.variable for obj in ro.objects]
            offset_to_vars[key] = variables

        s = []
        for offset, variables in offset_to_vars.iteritems():
            s.append("Offset %#x: %s" % (offset, variables))
        return "\n".join(s)

    def add_variable(self, start, variable):
        """
        Add a variable to this region at the given offset.

        :param int start:
        :param SimVariable variable:
        :return: None
        """

        self._store(start, variable, overwrite=False)

    def set_variable(self, start, variable):
        """
        Add a variable to this region at the given offset, and remove all other variables that are fully covered by
        this variable.

        :param int start:
        :param SimVariable variable:
        :return: None
        """

        self._store(start, variable, overwrite=True)

    def get_base_addr(self, addr):
        """
        Get the base offset (the key we are using to index variables covering the given offset) of a specific offset.

        :param int addr:
        :return:
        :rtype:  int or None
        """

        base_addr, container = self._get_container(addr)
        if container is None:
            return None
        else:
            return base_addr

    def get_variables_by_offset(self, start):
        """
        Find variables covering the given region offset.

        :param int start:
        :return: A list of stack variables.
        :rtype:  set
        """

        base_addr, container = self._get_container(start)
        if container is None:
            return []
        else:
            return container.variables

    #
    # Private methods
    #

    def _store(self, start, variable, overwrite=False):
        """
        Store a variable into the storage.

        :param int start: The beginning address of the variable.
        :param variable: The variable to store.
        :param bool overwrite: Whether existing variables should be overwritten or not.
        :return: None
        """

        loc_and_var = LocationAndVariable(start, variable)
        self.__store(loc_and_var, overwrite=overwrite)

    def __store(self, loc_and_var, overwrite=False, make_phi_func=None):
        """
        Store a variable into the storage.

        :param LocationAndVariable loc_and_var: The descriptor describing start address and the variable.
        :param bool overwrite: Whether existing variables should be overwritten or not.
        :return: None
        """

        start = loc_and_var.start
        variable = loc_and_var.variable
        variable_size = variable.size if variable.size is not None else 1
        end = start + variable_size

        # region items in the middle
        overlapping_items = list(self._storage.irange(start, end - 1))

        # is there a region item that begins before the start and overlaps with this variable?
        floor_key, floor_item = self._get_container(start)
        if floor_item is not None and floor_key not in overlapping_items:
            # insert it into the beginningq
            overlapping_items.insert(0, (floor_key, self._storage[floor_key]))

        # scan through the entire list of region items, split existing regions and insert new regions as needed
        to_update = {start: RegionObject(start, variable_size, {loc_and_var})}
        last_end = start

        for floor_key in overlapping_items:
            item = self._storage[floor_key]
            if item.start < start:
                # we need to break this item into two
                a, b = item.split(start)
                if overwrite:
                    b.set_object(loc_and_var)
                else:
                    self._add_object_or_make_phi(b,
                                                 loc_and_var,
                                                 make_phi_func=make_phi_func)
                to_update[a.start] = a
                to_update[b.start] = b
                last_end = b.end
            elif item.start > last_end:
                # there is a gap between the last item and the current item
                # fill in the gap
                new_item = RegionObject(last_end, item.start - last_end,
                                        {loc_and_var})
                to_update[new_item.start] = new_item
                last_end = new_item.end
            elif item.end > end:
                # we need to split this item into two
                a, b = item.split(end)
                if overwrite:
                    a.set_object(loc_and_var)
                else:
                    self._add_object_or_make_phi(a,
                                                 loc_and_var,
                                                 make_phi_func=make_phi_func)
                to_update[a.start] = a
                to_update[b.start] = b
                last_end = b.end
            else:
                if overwrite:
                    item.set_object(loc_and_var)
                else:
                    self._add_object_or_make_phi(item,
                                                 loc_and_var,
                                                 make_phi_func=make_phi_func)
                to_update[loc_and_var.start] = item

        self._storage.update(to_update)

    def _is_overlapping(self, start, variable):

        if variable.size is not None:
            # make sure this variable does not overlap with any other variable
            end = start + variable.size
            try:
                prev_offset = next(
                    self._storage.irange(maximum=end - 1, reverse=True))
            except StopIteration:
                prev_offset = None

            if prev_offset is not None:
                if start <= prev_offset < end:
                    return True
                prev_item = self._storage[prev_offset][0]
                prev_item_size = prev_item.size if prev_item.size is not None else 1
                if start < prev_offset + prev_item_size < end:
                    return True
        else:
            try:
                prev_offset = next(
                    self._storage.irange(maximum=start, reverse=True))
            except StopIteration:
                prev_offset = None

            if prev_offset is not None:
                prev_item = self._storage[prev_offset][0]
                prev_item_size = prev_item.size if prev_item.size is not None else 1
                if prev_offset <= start < prev_offset + prev_item_size:
                    return True

        return False

    def _add_object_or_make_phi(self, item, loc_and_var, make_phi_func=None):  #pylint:disable=no-self-use
        if not make_phi_func or len({loc_and_var.variable}
                                    | item.variables) == 1:
            item.add_object(loc_and_var)
        else:
            # make a phi node
            item.set_object(
                LocationAndVariable(
                    loc_and_var.start,
                    make_phi_func(loc_and_var.variable, *item.variables)))
Пример #26
0
class KeyedRegion:
    """
    KeyedRegion keeps a mapping between stack offsets and all objects covering that offset. It assumes no variable in
    this region overlap with another variable in this region.

    Registers and function frames can all be viewed as a keyed region.
    """

    __slots__ = ('_storage', '_object_mapping', '_phi_node_contains' )

    def __init__(self, tree=None, phi_node_contains=None):
        self._storage = SortedDict() if tree is None else tree
        self._object_mapping = weakref.WeakValueDictionary()
        self._phi_node_contains = phi_node_contains

    def __getstate__(self):
        return self._storage, dict(self._object_mapping), self._phi_node_contains

    def __setstate__(self, s):
        self._storage, om, self._phi_node_contains = s
        self._object_mapping = weakref.WeakValueDictionary(om)

    def _get_container(self, offset):
        try:
            base_offset = next(self._storage.irange(maximum=offset, reverse=True))
        except StopIteration:
            return offset, None
        else:
            container = self._storage[base_offset]
            if container.includes(offset):
                return base_offset, container
            return offset, None

    def __contains__(self, offset):
        """
        Test if there is at least one variable covering the given offset.

        :param offset:
        :return:
        """

        if type(offset) is not int:
            raise TypeError("KeyedRegion only accepts concrete offsets.")

        return self._get_container(offset)[1] is not None

    def __len__(self):
        return len(self._storage)

    def __iter__(self):
        return iter(self._storage.values())

    def __eq__(self, other):
        if set(self._storage.keys()) != set(other._storage.keys()):
            return False

        for k, v in self._storage.items():
            if v != other._storage[k]:
                return False

        return True

    def copy(self):
        if not self._storage:
            return KeyedRegion(phi_node_contains=self._phi_node_contains)

        kr = KeyedRegion(phi_node_contains=self._phi_node_contains)
        for key, ro in self._storage.items():
            kr._storage[key] = ro.copy()
        kr._object_mapping = self._object_mapping.copy()
        return kr

    def merge(self, other, replacements=None):
        """
        Merge another KeyedRegion into this KeyedRegion.

        :param KeyedRegion other: The other instance to merge with.
        :return: None
        """

        # TODO: is the current solution not optimal enough?
        for _, item in other._storage.items():  # type: RegionObject
            for so in item.stored_objects:  # type: StoredObject
                if replacements and so.obj in replacements:
                    so = StoredObject(so.start, replacements[so.obj], so.size)
                self._object_mapping[so.obj_id] = so
                self.__store(so, overwrite=False)

        return self

    def replace(self, replacements):
        """
        Replace variables with other variables.

        :param dict replacements:   A dict of variable replacements.
        :return:                    self
        """

        for old_var, new_var in replacements.items():
            old_var_id = id(old_var)
            if old_var_id in self._object_mapping:
                # FIXME: we need to check if old_var still exists in the storage
                old_so = self._object_mapping[old_var_id]  # type: StoredObject
                self._store(old_so.start, new_var, old_so.size, overwrite=True)

        return self

    def dbg_repr(self):
        """
        Get a debugging representation of this keyed region.
        :return: A string of debugging output.
        """
        keys = self._storage.keys()
        offset_to_vars = { }

        for key in sorted(keys):
            ro = self._storage[key]
            variables = [ obj.obj for obj in ro.stored_objects ]
            offset_to_vars[key] = variables

        s = [ ]
        for offset, variables in offset_to_vars.items():
            s.append("Offset %#x: %s" % (offset, variables))
        return "\n".join(s)

    def add_variable(self, start, variable):
        """
        Add a variable to this region at the given offset.

        :param int start:
        :param SimVariable variable:
        :return: None
        """

        size = variable.size if variable.size is not None else 1

        self.add_object(start, variable, size)

    def add_object(self, start, obj, object_size):
        """
        Add/Store an object to this region at the given offset.

        :param start:
        :param obj:
        :param int object_size: Size of the object
        :return:
        """

        self._store(start, obj, object_size, overwrite=False)

    def set_variable(self, start, variable):
        """
        Add a variable to this region at the given offset, and remove all other variables that are fully covered by
        this variable.

        :param int start:
        :param SimVariable variable:
        :return: None
        """

        size = variable.size if variable.size is not None else 1

        self.set_object(start, variable, size)

    def set_object(self, start, obj, object_size):
        """
        Add an object to this region at the given offset, and remove all other objects that are fully covered by this
        object.

        :param start:
        :param obj:
        :param object_size:
        :return:
        """

        self._store(start, obj, object_size, overwrite=True)

    def get_base_addr(self, addr):
        """
        Get the base offset (the key we are using to index objects covering the given offset) of a specific offset.

        :param int addr:
        :return:
        :rtype:  int or None
        """

        base_addr, container = self._get_container(addr)
        if container is None:
            return None
        else:
            return base_addr

    def get_variables_by_offset(self, start):
        """
        Find variables covering the given region offset.

        :param int start:
        :return: A list of stack variables.
        :rtype:  set
        """

        _, container = self._get_container(start)
        if container is None:
            return []
        else:
            return container.internal_objects

    def get_objects_by_offset(self, start):
        """
        Find objects covering the given region offset.

        :param start:
        :return:
        """

        _, container = self._get_container(start)
        if container is None:
            return set()
        else:
            return container.internal_objects

    #
    # Private methods
    #

    def _store(self, start, obj, size, overwrite=False):
        """
        Store a variable into the storage.

        :param int start: The beginning address of the variable.
        :param obj: The object to store.
        :param int size: Size of the object to store.
        :param bool overwrite: Whether existing objects should be overwritten or not.
        :return: None
        """

        stored_object = StoredObject(start, obj, size)
        self._object_mapping[stored_object.obj_id] = stored_object
        self.__store(stored_object, overwrite=overwrite)

    def __store(self, stored_object, overwrite=False):
        """
        Store a variable into the storage.

        :param StoredObject stored_object: The descriptor describing start address and the variable.
        :param bool overwrite:  Whether existing objects should be overwritten or not. True to make a strong update,
                                False to make a weak update.
        :return: None
        """

        start = stored_object.start
        object_size = stored_object.size
        end = start + object_size

        # region items in the middle
        overlapping_items = list(self._storage.irange(start, end-1))

        # is there a region item that begins before the start and overlaps with this variable?
        floor_key, floor_item = self._get_container(start)
        if floor_item is not None and floor_key not in overlapping_items:
            # insert it into the beginning
            overlapping_items.insert(0, floor_key)

        # scan through the entire list of region items, split existing regions and insert new regions as needed
        to_update = {start: RegionObject(start, object_size, {stored_object})}
        last_end = start

        for floor_key in overlapping_items:
            item = self._storage[floor_key]
            if item.start < start:
                # we need to break this item into two
                a, b = item.split(start)
                if overwrite:
                    b.set_object(stored_object)
                else:
                    self._add_object_with_check(b, stored_object)
                to_update[a.start] = a
                to_update[b.start] = b
                last_end = b.end
            elif item.start > last_end:
                # there is a gap between the last item and the current item
                # fill in the gap
                new_item = RegionObject(last_end, item.start - last_end, {stored_object})
                to_update[new_item.start] = new_item
                last_end = new_item.end
            elif item.end > end:
                # we need to split this item into two
                a, b = item.split(end)
                if overwrite:
                    a.set_object(stored_object)
                else:
                    self._add_object_with_check(a, stored_object)
                to_update[a.start] = a
                to_update[b.start] = b
                last_end = b.end
            else:
                if overwrite:
                    item.set_object(stored_object)
                else:
                    self._add_object_with_check(item, stored_object)
                to_update[item.start] = item

        self._storage.update(to_update)

    def _is_overlapping(self, start, variable):

        if variable.size is not None:
            # make sure this variable does not overlap with any other variable
            end = start + variable.size
            try:
                prev_offset = next(self._storage.irange(maximum=end-1, reverse=True))
            except StopIteration:
                prev_offset = None

            if prev_offset is not None:
                if start <= prev_offset < end:
                    return True
                prev_item = self._storage[prev_offset][0]
                prev_item_size = prev_item.size if prev_item.size is not None else 1
                if start < prev_offset + prev_item_size < end:
                    return True
        else:
            try:
                prev_offset = next(self._storage.irange(maximum=start, reverse=True))
            except StopIteration:
                prev_offset = None

            if prev_offset is not None:
                prev_item = self._storage[prev_offset][0]
                prev_item_size = prev_item.size if prev_item.size is not None else 1
                if prev_offset <= start < prev_offset + prev_item_size:
                    return True

        return False

    def _add_object_with_check(self, item, stored_object):
        if len({stored_object.obj} | item.internal_objects) > 1:
            if self._phi_node_contains is not None:
                # check if `item` is a phi node that contains stored_object.obj
                for so in item.internal_objects:
                    if self._phi_node_contains(so, stored_object.obj):
                        # yes! so we want to skip this object
                        return
                # check if `stored_object.obj` is a phi node that contains item.internal_objects
                if all(self._phi_node_contains(stored_object.obj, o) for o in item.internal_objects):
                    # yes!
                    item.set_object(stored_object)
                    return

            l.warning("Overlapping objects %s.", str({stored_object.obj} | item.internal_objects))
            # import ipdb; ipdb.set_trace()
        item.add_object(stored_object)
Пример #27
0
class CFBlanket(Analysis):
    """
    A Control-Flow Blanket is a representation for storing all instructions, data entries, and bytes of a full program.
    """
    def __init__(self, cfg=None):
        self._blanket = SortedDict()

        self._regions = []

        self._init_regions()

        if cfg is not None:
            self._from_cfg(cfg)
        else:
            _l.debug(
                "CFG is not specified. Initialize CFBlanket from the knowledge base."
            )
            for func in self.kb.functions.values():
                self.add_function(func)

    def _init_regions(self):

        for obj in self.project.loader.all_objects:
            if isinstance(obj, cle.MetaELF):
                if obj.sections:
                    # Enumerate sections in an ELF file
                    for section in obj.sections:
                        if section.occupies_memory:
                            mr = MemoryRegion(section.vaddr, section.memsize,
                                              'TODO', obj, section)
                            self._regions.append(mr)
                else:
                    raise NotImplementedError()
            else:
                mr = MemoryRegion(obj.min_addr,
                                  obj.size if hasattr(obj, 'size') else 80,
                                  'TODO', obj, None)
                self._regions.append(mr)

        # Sort them just in case
        self._regions = list(sorted(self._regions, key=lambda x: x.addr))

    @property
    def regions(self):
        """
        Return all memory regions.
        """

        return self._regions

    def floor_addr(self, addr):
        try:
            return next(self._blanket.irange(maximum=addr, reverse=True))
        except StopIteration:
            raise KeyError(addr)

    def floor_item(self, addr):
        key = self.floor_addr(addr)
        return key, self._blanket[key]

    def floor_items(self, addr=None, reverse=False):
        if addr is None:
            start_addr = None
        else:
            try:
                start_addr = next(
                    self._blanket.irange(maximum=addr, reverse=True))
            except StopIteration:
                start_addr = addr

        for key in self._blanket.irange(minimum=start_addr, reverse=reverse):
            yield key, self._blanket[key]

    def ceiling_addr(self, addr):
        try:
            return next(self._blanket.irange(minimum=addr))
        except StopIteration:
            raise KeyError(addr)

    def ceiling_item(self, addr):
        key = self.ceiling_addr(addr)
        return key, self._blanket[key]

    def ceiling_items(self, addr=None, reverse=False, include_first=True):
        if addr is None:
            start_addr = None
        else:
            try:
                start_addr = next(self._blanket.irange(minimum=addr))
            except StopIteration:
                start_addr = addr

        for key in self._blanket.irange(
                maximum=start_addr if include_first else start_addr - 1,
                reverse=reverse):
            yield key, self._blanket[key]

    def __getitem__(self, addr):
        return self._blanket[addr]

    def add_obj(self, addr, obj):
        """
        Adds an object `obj` to the blanket at the specified address `addr`
        """
        self._blanket[addr] = obj

    def add_function(self, func):
        """
        Add a function `func` and all blocks of this function to the blanket.
        """
        for block in func.blocks:
            self.add_obj(block.addr, block)

    def dbg_repr(self):
        """
        The debugging representation of this CFBlanket.

        :return:    The debugging representation of this CFBlanket.
        :rtype:     str
        """

        output = []

        for obj in self.project.loader.all_objects:
            for section in obj.sections:
                if section.memsize == 0:
                    continue
                min_addr, max_addr = section.min_addr, section.max_addr
                output.append("### Object %s" % repr(section))
                output.append("### Range %#x-%#x" % (min_addr, max_addr))

                pos = min_addr
                while pos < max_addr:
                    try:
                        addr, thing = self.floor_item(pos)
                        output.append("%#x: %s" % (addr, repr(thing)))

                        if thing.size == 0: pos += 1
                        else: pos += thing.size
                    except KeyError:
                        pos += 1

                output.append("")

        return "\n".join(output)

    def _from_cfg(self, cfg):
        """
        Initialize CFBlanket from a CFG instance.

        :param cfg: A CFG instance.
        :return:    None
        """

        # Let's first add all functions first
        for func in cfg.kb.functions.values():
            self.add_function(func)

        self._mark_unknowns()

    def _mark_unknowns(self):
        """
        Mark all unmapped regions.

        :return: None
        """

        for obj in self.project.loader.all_objects:
            if isinstance(obj, cle.ELF):
                # sections?
                if obj.sections:
                    for section in obj.sections:
                        if not section.memsize or not section.vaddr:
                            continue
                        min_addr, max_addr = section.min_addr, section.max_addr
                        self._mark_unknowns_core(min_addr,
                                                 max_addr + 1,
                                                 obj=obj,
                                                 section=section)
                elif obj.segments:
                    for segment in obj.segments:
                        if not segment.memsize:
                            continue
                        min_addr, max_addr = segment.min_addr, segment.max_addr
                        self._mark_unknowns_core(min_addr,
                                                 max_addr + 1,
                                                 obj=obj,
                                                 segment=segment)
                else:
                    # is it empty?
                    _l.warning("Empty ELF object %s.", repr(obj))
            elif isinstance(obj, cle.PE):
                if obj.sections:
                    for section in obj.sections:
                        if not section.memsize:
                            continue
                        min_addr, max_addr = section.min_addr, section.max_addr
                        self._mark_unknowns_core(min_addr,
                                                 max_addr + 1,
                                                 obj=obj,
                                                 section=section)
                else:
                    # is it empty?
                    _l.warning("Empty PE object %s.", repr(obj))
            else:
                min_addr, max_addr = obj.min_addr, obj.max_addr
                self._mark_unknowns_core(min_addr, max_addr + 1, obj=obj)

    def _mark_unknowns_core(self,
                            min_addr,
                            max_addr,
                            obj=None,
                            segment=None,
                            section=None):

        # The region should be [min_addr, max_addr)

        try:
            addr = self.floor_addr(min_addr)
            if addr < min_addr:
                raise KeyError
        except KeyError:
            # there is no other lower address
            try:
                next_addr = self.ceiling_addr(min_addr)
                if next_addr >= max_addr:
                    raise KeyError
            except KeyError:
                next_addr = max_addr

            size = next_addr - min_addr
            if obj is None or isinstance(obj, cle.ExternObject):
                bytes_ = None
            else:
                try:
                    _l.debug(
                        "Loading bytes from object %s, section %s, segmeng %s, addresss %#x.",
                        obj, section, segment, min_addr)
                    bytes_ = self.project.loader.memory.load(min_addr, size)
                except KeyError:
                    # The address does not exist
                    bytes_ = None
            self.add_obj(
                min_addr,
                Unknown(min_addr,
                        size,
                        bytes_=bytes_,
                        object_=obj,
                        segment=segment,
                        section=section))

        addr = min_addr
        while addr < max_addr:
            last_addr, last_item = self.floor_item(addr)
            if last_addr < min_addr:
                # impossible
                raise Exception('Impossible')

            if last_item.size == 0:
                # Make sure everything has a non-zero size
                last_item_size = 1
            else:
                last_item_size = last_item.size
            end_addr = last_addr + last_item_size
            if end_addr < max_addr:
                try:
                    next_addr = self.ceiling_addr(end_addr)
                except KeyError:
                    next_addr = max_addr
                if next_addr > end_addr:
                    # there is a gap
                    size = next_addr - end_addr
                    if obj is None or isinstance(obj, cle.ExternObject):
                        bytes_ = None
                    else:
                        try:
                            _l.debug(
                                "Loading bytes from object %s, section %s, segmeng %s, addresss %#x.",
                                obj, section, segment, next_addr)
                            bytes_ = self.project.loader.memory.load(
                                next_addr, size)
                        except KeyError:
                            # The address does not exist
                            bytes_ = None
                    self.add_obj(
                        end_addr,
                        Unknown(end_addr,
                                size,
                                bytes_=bytes_,
                                object_=obj,
                                segment=segment,
                                section=section))
                addr = next_addr
            else:
                addr = max_addr
Пример #28
0
class CFBlanket(Analysis):
    """
    A Control-Flow Blanket is a representation for storing all instructions, data entries, and bytes of a full program.

    Region types:
    - section
    - segment
    - extern
    - tls
    - kernel
    """
    def __init__(self, exclude_region_types=None):
        self._blanket = SortedDict()

        self._regions = []
        self._exclude_region_types = set(
        ) if not exclude_region_types else exclude_region_types

        self._init_regions()

        # initialize
        for func in self.kb.functions.values():
            self.add_function(func)
        self._mark_memory_data()
        self._mark_unknowns()

    def _init_regions(self):

        for obj in self.project.loader.all_objects:
            if isinstance(obj, cle.MetaELF):
                if obj.sections:
                    if "section" not in self._exclude_region_types:
                        # Enumerate sections in an ELF file
                        for section in obj.sections:
                            if section.occupies_memory:
                                mr = MemoryRegion(section.vaddr,
                                                  section.memsize, 'section',
                                                  obj, section)
                                self._regions.append(mr)
                else:
                    raise NotImplementedError(
                        "Currently ELFs without sections are not supported. Please implement or "
                        "complain on GitHub.")
            elif isinstance(obj, cle.PE):
                if obj.sections:
                    if "section" not in self._exclude_region_types:
                        for section in obj.sections:
                            mr = MemoryRegion(section.vaddr, section.memsize,
                                              'section', obj, section)
                            self._regions.append(mr)
                else:
                    raise NotImplementedError(
                        "Currently PEs without sections are not supported. Please report to "
                        "GitHub and provide an example binary.")
            elif isinstance(obj, KernelObject):
                if "kernel" not in self._exclude_region_types:
                    size = obj.max_addr - obj.min_addr
                    mr = MemoryRegion(obj.min_addr, size, "kernel", obj, None)
                    self._regions.append(mr)
            elif isinstance(obj, ExternObject):
                if "extern" not in self._exclude_region_types:
                    size = obj.max_addr - obj.min_addr
                    mr = MemoryRegion(obj.min_addr, size, "extern", obj, None)
                    self._regions.append(mr)
            elif isinstance(obj, ELFTLSObject):
                if "tls" not in self._exclude_region_types:
                    size = obj.max_addr - obj.min_addr
                    mr = MemoryRegion(obj.min_addr, size, "tls", obj, None)
                    self._regions.append(mr)
            else:
                if hasattr(obj, "size"):
                    size = obj.size
                else:
                    size = obj.max_addr - obj.min_addr
                type_ = "TODO"
                mr = MemoryRegion(obj.min_addr, size, type_, obj, obj)
                self._regions.append(mr)

        # Sort them just in case
        self._regions = list(sorted(self._regions, key=lambda x: x.addr))

    @property
    def regions(self):
        """
        Return all memory regions.
        """

        return self._regions

    def floor_addr(self, addr):
        try:
            return next(self._blanket.irange(maximum=addr, reverse=True))
        except StopIteration:
            raise KeyError(addr)

    def floor_item(self, addr):
        key = self.floor_addr(addr)
        return key, self._blanket[key]

    def floor_items(self, addr=None, reverse=False):
        if addr is None:
            start_addr = None
        else:
            try:
                start_addr = next(
                    self._blanket.irange(maximum=addr, reverse=True))
            except StopIteration:
                start_addr = addr

        for key in self._blanket.irange(minimum=start_addr, reverse=reverse):
            yield key, self._blanket[key]

    def ceiling_addr(self, addr):
        try:
            return next(self._blanket.irange(minimum=addr))
        except StopIteration:
            raise KeyError(addr)

    def ceiling_item(self, addr):
        key = self.ceiling_addr(addr)
        return key, self._blanket[key]

    def ceiling_items(self, addr=None, reverse=False, include_first=True):
        if addr is None:
            start_addr = None
        else:
            try:
                start_addr = next(self._blanket.irange(minimum=addr))
            except StopIteration:
                start_addr = addr

        for key in self._blanket.irange(
                maximum=start_addr if include_first else start_addr - 1,
                reverse=reverse):
            yield key, self._blanket[key]

    def __getitem__(self, addr):
        return self._blanket[addr]

    def add_obj(self, addr, obj):
        """
        Adds an object `obj` to the blanket at the specified address `addr`
        """
        self._blanket[addr] = obj

    def add_function(self, func):
        """
        Add a function `func` and all blocks of this function to the blanket.
        """
        for block in func.blocks:
            self.add_obj(block.addr, block)

    def dbg_repr(self):
        """
        The debugging representation of this CFBlanket.

        :return:    The debugging representation of this CFBlanket.
        :rtype:     str
        """

        output = []

        for obj in self.project.loader.all_objects:
            for section in obj.sections:
                if section.memsize == 0:
                    continue
                min_addr, max_addr = section.min_addr, section.max_addr
                output.append("### Object %s" % repr(section))
                output.append("### Range %#x-%#x" % (min_addr, max_addr))

                pos = min_addr
                while pos < max_addr:
                    try:
                        addr, thing = self.floor_item(pos)
                        output.append("%#x: %s" % (addr, repr(thing)))

                        if thing.size == 0: pos += 1
                        else: pos += thing.size
                    except KeyError:
                        pos += 1

                output.append("")

        return "\n".join(output)

    def _mark_memory_data(self):
        """
        Mark all memory data.

        :return: None
        """
        if 'CFGFast' not in self.kb.cfgs:
            return
        cfg_model = self.kb.cfgs['CFGFast']

        for addr, memory_data in cfg_model.memory_data.items():
            memory_data: MemoryData
            if memory_data.sort == MemoryDataSort.CodeReference:
                # skip Code Reference
                continue
            self.add_obj(addr, memory_data)

    def _mark_unknowns(self):
        """
        Mark all unmapped regions.

        :return: None
        """

        for obj in self.project.loader.all_objects:
            if isinstance(obj, cle.ELF):
                # sections?
                if obj.sections and "section" not in self._exclude_region_types:
                    for section in obj.sections:
                        if not section.memsize or not section.vaddr:
                            continue
                        min_addr, max_addr = section.min_addr, section.max_addr
                        self._mark_unknowns_core(min_addr,
                                                 max_addr + 1,
                                                 obj=obj,
                                                 section=section)
                elif obj.segments and "segment" not in self._exclude_region_types:
                    for segment in obj.segments:
                        if not segment.memsize:
                            continue
                        min_addr, max_addr = segment.min_addr, segment.max_addr
                        self._mark_unknowns_core(min_addr,
                                                 max_addr + 1,
                                                 obj=obj,
                                                 segment=segment)
                else:
                    # is it empty?
                    _l.warning("Empty ELF object %s.", repr(obj))
            elif isinstance(obj, cle.PE):
                if obj.sections:
                    for section in obj.sections:
                        if not section.memsize:
                            continue
                        min_addr, max_addr = section.min_addr, section.max_addr
                        self._mark_unknowns_core(min_addr,
                                                 max_addr + 1,
                                                 obj=obj,
                                                 section=section)
                else:
                    # is it empty?
                    _l.warning("Empty PE object %s.", repr(obj))
            elif isinstance(obj, ELFTLSObject):
                if "tls" in self._exclude_region_types:
                    # Skip them for now
                    pass
                else:
                    min_addr, max_addr = obj.min_addr, obj.max_addr
                    self._mark_unknowns_core(min_addr, max_addr + 1, obj=obj)
            elif isinstance(obj, KernelObject):
                if "kernel" in self._exclude_region_types:
                    # skip
                    pass
                else:
                    min_addr, max_addr = obj.min_addr, obj.max_addr
                    self._mark_unknowns_core(min_addr, max_addr + 1, obj=obj)
            else:
                min_addr, max_addr = obj.min_addr, obj.max_addr
                self._mark_unknowns_core(min_addr, max_addr + 1, obj=obj)

    def _mark_unknowns_core(self,
                            min_addr,
                            max_addr,
                            obj=None,
                            segment=None,
                            section=None):

        # The region should be [min_addr, max_addr)

        try:
            addr = self.floor_addr(min_addr)
            if addr < min_addr:
                raise KeyError
        except KeyError:
            # there is no other lower address
            try:
                next_addr = self.ceiling_addr(min_addr)
                if next_addr >= max_addr:
                    raise KeyError
            except KeyError:
                next_addr = max_addr

            size = next_addr - min_addr
            if obj is None or isinstance(obj, cle.ExternObject):
                bytes_ = None
            else:
                try:
                    _l.debug(
                        "Loading bytes from object %s, section %s, segmeng %s, addresss %#x.",
                        obj, section, segment, min_addr)
                    bytes_ = self.project.loader.memory.load(min_addr, size)
                except KeyError:
                    # The address does not exist
                    bytes_ = None
            self.add_obj(
                min_addr,
                Unknown(min_addr,
                        size,
                        bytes_=bytes_,
                        object_=obj,
                        segment=segment,
                        section=section))

        addr = min_addr
        while addr < max_addr:
            last_addr, last_item = self.floor_item(addr)
            if last_addr < min_addr:
                # impossible
                raise Exception('Impossible')

            if last_item.size == 0:
                # Make sure everything has a non-zero size
                last_item_size = 1
            else:
                last_item_size = last_item.size
            end_addr = last_addr + last_item_size
            if end_addr < max_addr:
                try:
                    next_addr = self.ceiling_addr(end_addr)
                except KeyError:
                    next_addr = max_addr
                if next_addr > end_addr:
                    # there is a gap
                    size = next_addr - end_addr
                    if obj is None or isinstance(obj, cle.ExternObject):
                        bytes_ = None
                    else:
                        try:
                            _l.debug(
                                "Loading bytes from object %s, section %s, segmeng %s, addresss %#x.",
                                obj, section, segment, next_addr)
                            bytes_ = self.project.loader.memory.load(
                                next_addr, size)
                        except KeyError:
                            # The address does not exist
                            bytes_ = None
                    self.add_obj(
                        end_addr,
                        Unknown(end_addr,
                                size,
                                bytes_=bytes_,
                                object_=obj,
                                segment=segment,
                                section=section))
                addr = next_addr
            else:
                addr = max_addr
Пример #29
0
class TreePage(BasePage):
    """
    Page object, implemented with a sorted dict. Who knows what's underneath!
    """

    def __init__(self, *args, **kwargs):
        storage = kwargs.pop("storage", None)
        super(TreePage, self).__init__(*args, **kwargs)
        self._storage = SortedDict() if storage is None else storage

    def keys(self):
        if len(self._storage) == 0:
            return set()
        else:
            return set.union(*(set(range(*self._resolve_range(mo))) for mo in self._storage.itervalues()))

    def replace_mo(self, state, old_mo, new_mo):
        start, end = self._resolve_range(old_mo)
        for key in self._storage.irange(start, end-1):
            val = self._storage[key]
            if val is old_mo:
                #assert new_mo.includes(a)
                self._storage[key] = new_mo

    def store_overwrite(self, state, new_mo, start, end):
        # iterate over each item we might overwrite
        # track our mutations separately since we're in the process of iterating
        deletes = []
        updates = { start: new_mo }

        for key in self._storage.irange(maximum=end-1, reverse=True):
            old_mo = self._storage[key]

            # make sure we aren't overwriting all of an item that overlaps the end boundary
            if end < self._page_addr + self._page_size and end not in updates and old_mo.includes(end):
                updates[end] = old_mo

            # we can't set a minimum on the range because we need to do the above for
            # the first object before start too
            if key < start:
                break

            # delete any key that falls within the range
            deletes.append(key)

        #assert all(m.includes(i) for i,m in updates.items())

        # perform mutations
        for key in deletes:
            del self._storage[key]

        self._storage.update(updates)

    def store_underwrite(self, state, new_mo, start, end):
        # track the point that we need to write up to
        last_missing = end - 1
        # track also updates since we can't update while iterating
        updates = {}

        for key in self._storage.irange(maximum=end-1, reverse=True):
            mo = self._storage[key]

            # if the mo stops
            if mo.base <= last_missing and not mo.includes(last_missing):
                updates[max(mo.last_addr+1, start)] = new_mo
            last_missing = mo.base - 1

            # we can't set a minimum on the range because we need to do the above for
            # the first object before start too
            if last_missing < start:
                break

        # if there are no memory objects <= start, we won't have filled start yet
        if last_missing >= start:
            updates[start] = new_mo

        #assert all(m.includes(i) for i,m in updates.items())

        self._storage.update(updates)

    def load_mo(self, state, page_idx):
        """
        Loads a memory object from memory.

        :param page_idx: the index into the page
        :returns: a tuple of the object
        """

        try:
            key = next(self._storage.irange(maximum=page_idx, reverse=True))
        except StopIteration:
            return None
        else:
            return self._storage[key]

    def load_slice(self, state, start, end):
        """
        Return the memory objects overlapping with the provided slice.

        :param start: the start address
        :param end: the end address (non-inclusive)
        :returns: tuples of (starting_addr, memory_object)
        """
        keys = list(self._storage.irange(start, end-1))
        if not keys or keys[0] != start:
            try:
                key = next(self._storage.irange(maximum=start, reverse=True))
            except StopIteration:
                pass
            else:
                if self._storage[key].includes(start):
                    items.insert(0, key)
        return [(key, self._storage[key]) for key in keys]

    def _copy_args(self):
        return { 'storage': self._storage.copy() }
Пример #30
0
class QLinearDisassembly(QDisassemblyBaseControl, QAbstractScrollArea):
    OBJECT_PADDING = 0

    def __init__(self, workspace, disasm_view, parent=None):
        QDisassemblyBaseControl.__init__(self, workspace, disasm_view, QAbstractScrollArea)
        QAbstractScrollArea.__init__(self, parent=parent)

        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.horizontalScrollBar().setSingleStep(Conf.disasm_font_width)
        self.verticalScrollBar().setSingleStep(16)

        # self.setTransformationAnchor(QGraphicsView.NoAnchor)
        # self.setResizeAnchor(QGraphicsView.NoAnchor)
        # self.setAlignment(Qt.AlignLeft)

        self._viewer = None  # type: QLinearDisassemblyView

        self._line_height = Conf.disasm_font_height

        self._offset_to_region = SortedDict()
        self._addr_to_region_offset = SortedDict()
        # Offset (in bytes) into the entire blanket view
        self._offset = 0
        # The maximum offset (in bytes) of the blanket view
        self._max_offset = None
        # The first line that is rendered of the first object in self.objects. Start from 0.
        self._start_line_in_object = 0

        self._disasms = { }
        self.objects = [ ]

        self.verticalScrollBar().actionTriggered.connect(self._on_vertical_scroll_bar_triggered)

        self._init_widgets()

    def reload(self):
        self.initialize()

    #
    # Properties
    #

    @property
    def offset(self):
        return self._offset

    @offset.setter
    def offset(self, v):
        self._offset = v

    @property
    def max_offset(self):
        if self._max_offset is None:
            self._max_offset = self._calculate_max_offset()
        return self._max_offset

    @property
    def cfg(self):
        return self.workspace.instance.cfg

    @property
    def cfb(self):
        return self.workspace.instance.cfb

    @property
    def scene(self):
        return self._viewer._scene

    #
    # Events
    #

    def resizeEvent(self, event):
        old_height = event.oldSize().height()
        new_height = event.size().height()
        self._viewer._scene.setSceneRect(QRectF(0, 0, event.size().width(), new_height))

        if new_height > old_height:
            # we probably need more objects generated
            curr_offset = self._offset
            self._offset = None  # force a re-generation of objects
            self.prepare_objects(curr_offset, start_line=self._start_line_in_object)
            self.redraw()

        super().resizeEvent(event)

    def wheelEvent(self, event):
        """
        :param QWheelEvent event:
        :return:
        """
        delta = event.delta()
        if delta < 0:
            # scroll down by some lines
            lines = min(int(-delta // self._line_height), 3)
            self.prepare_objects(self.offset, start_line=self._start_line_in_object + lines)
        elif delta > 0:
            # Scroll up by some lines
            lines = min(int(delta // self._line_height), 3)
            self.prepare_objects(self.offset, start_line=self._start_line_in_object - lines)

        self.verticalScrollBar().setValue(self.offset * self._line_height)
        event.accept()
        self.viewport().update()

    def _on_vertical_scroll_bar_triggered(self, action):

        if action == QAbstractSlider.SliderSingleStepAdd:
            # scroll down by one line
            self.prepare_objects(self.offset, start_line=self._start_line_in_object + 1)
            self.viewport().update()
        elif action == QAbstractSlider.SliderSingleStepSub:
            # Scroll up by one line
            self.prepare_objects(self.offset, start_line=self._start_line_in_object - 1)
            self.viewport().update()
        elif action == QAbstractSlider.SliderPageStepAdd:
            # Scroll down by one page
            lines_per_page = int(self.height() // self._line_height)
            self.prepare_objects(self.offset, start_line=self._start_line_in_object
                                                                       + lines_per_page)
            self.viewport().update()
        elif action == QAbstractSlider.SliderPageStepSub:
            # Scroll up by one page
            lines_per_page = int(self.height() // self._line_height)
            self.prepare_objects(self.offset,
                                        start_line=self._start_line_in_object - lines_per_page)
            self.viewport().update()
        elif action == QAbstractSlider.SliderMove:
            # Setting a new offset
            new_offset = int(self.verticalScrollBar().value() // self._line_height)
            self.prepare_objects(new_offset)
            self.viewport().update()

    #
    # Public methods
    #

    def redraw(self):
        if self._viewer is not None:
            self._viewer.redraw()

    def refresh(self):
        self._update_size()
        self.redraw()

    def initialize(self):

        if self.cfb is None:
            return

        self._addr_to_region_offset.clear()
        self._offset_to_region.clear()
        self._disasms.clear()
        self._offset = None
        self._max_offset = None
        self._start_line_in_object = 0

        # enumerate memory regions
        byte_offset = 0
        for mr in self.cfb.regions:  # type: MemoryRegion
            if mr.type in {'tls', 'kernel'}:
                # Skip TLS objects and kernel objects
                continue
            self._addr_to_region_offset[mr.addr] = byte_offset
            self._offset_to_region[byte_offset] = mr
            byte_offset += mr.size

        self._update_size()

    def goto_function(self, func):
        if func.addr not in self._block_addr_map:
            _l.error('Unable to find entry block for function %s', func)
        view_height = self.viewport().height()
        desired_center_y = self._block_addr_map[func.addr].pos().y()
        _l.debug('Going to function at 0x%x by scrolling to %s', func.addr, desired_center_y)
        self.verticalScrollBar().setValue(desired_center_y - (view_height / 3))

    def show_instruction(self, insn_addr, insn_pos=None, centering=False, use_block_pos=False):
        """

        :param insn_addr:
        :param QGraphicsItem item:
        :param centering:
        :param use_block_pos:
        :return:
        """

        if insn_pos is not None:
            # check if item is already visible in the viewport
            viewport = self._viewer.viewport()
            rect = self._viewer.mapToScene(QRect(0, 0, viewport.width(), viewport.height())).boundingRect()
            if rect.contains(insn_pos):
                return

        self.navigate_to_addr(insn_addr)

    def navigate_to_addr(self, addr):
        if not self._addr_to_region_offset:
            return
        try:
            floor_region_addr = next(self._addr_to_region_offset.irange(maximum=addr, reverse=True))
        except StopIteration:
            floor_region_addr = next(self._addr_to_region_offset.irange())
        floor_region_offset = self._addr_to_region_offset[floor_region_addr]

        offset_into_region = addr - floor_region_addr
        self.navigate_to(floor_region_offset + offset_into_region)

    def navigate_to(self, offset):
        self.verticalScrollBar().setValue(offset * self._line_height)
        self.prepare_objects(offset, start_line=0)

    #
    # Private methods
    #

    def _init_widgets(self):
        self._viewer = QLinearDisassemblyView(self)

        layout = QHBoxLayout()
        layout.addWidget(self._viewer)
        layout.setContentsMargins(0, 0, 0, 0)

        self.setLayout(layout)

    def _update_size(self):

        # ask all objects to update their sizes
        for obj in self.objects:
            obj.clear_cache()
            obj.refresh()

        # update vertical scrollbar
        self.verticalScrollBar().setRange(0, self.max_offset * self._line_height - self.height() // 2)
        offset = 0 if self.offset is None else self.offset
        self.verticalScrollBar().setValue(offset * self._line_height)

    def clear_objects(self):
        self.objects.clear()
        self._offset = None

    def prepare_objects(self, offset, start_line=0):
        """
        Prepare objects to print based on offset and start_line. Update self.objects, self._offset, and
        self._start_line_in_object.
        :param int offset:      Beginning offset (in bytes) to display in the linear viewer.
        :param int start_line:  The first line into the first object to display in the linear viewer.
        :return:                None
        """

        if offset is None:
            offset = 0

        if offset == self._offset and start_line == self._start_line_in_object:
            return

        # Convert the offset to memory region
        base_offset, mr = self._region_from_offset(offset)  # type: int, MemoryRegion
        if mr is None:
            return

        addr = self._addr_from_offset(mr, base_offset, offset)
        _l.debug("Address %#x, offset %d, start_line %d.", addr, offset, start_line)

        self._insaddr_to_block.clear()

        if start_line < 0:
            # Which object are we currently displaying at the top of the disassembly view?
            try:
                top_obj_addr = self.cfb.floor_addr(addr=addr)
            except KeyError:
                top_obj_addr = addr

            # Reverse-iterate until we have enough lines to compensate start_line
            for obj_addr, obj in self.cfb.ceiling_items(addr=top_obj_addr, reverse=True, include_first=False):
                qobject = self._obj_to_paintable(obj_addr, obj)
                if qobject is None:
                    continue
                object_lines = int(qobject.height // self._line_height)
                _l.debug("Compensating negative start_line: object %s, object_lines %d.", obj, object_lines)
                start_line += object_lines
                if start_line >= 0:
                    addr = obj_addr
                    # Update offset
                    new_region_addr = next(self._addr_to_region_offset.irange(maximum=addr, reverse=True))
                    new_region_offset = self._addr_to_region_offset[new_region_addr]
                    offset = (addr - new_region_addr) + new_region_offset
                    break
            else:
                # umm we don't have enough objects to compensate the negative start_line
                start_line = 0
                # update addr and offset to their minimal values
                addr = next(self._addr_to_region_offset.irange())
                offset = self._addr_to_region_offset[addr]

        _l.debug("After adjustment: Address %#x, offset %d, start_line %d.", addr, offset, start_line)

        scene = self.scene
        # remove existing objects
        for obj in self.objects:
            scene.removeItem(obj)
        self.objects = [ ]

        viewable_lines = int(self.height() // self._line_height)
        lines = 0
        start_line_in_object = 0

        # Load a page of objects
        x = 80
        y = -start_line * self._line_height

        for obj_addr, obj in self.cfb.floor_items(addr=addr):
            qobject = self._obj_to_paintable(obj_addr, obj)
            _l.debug("Converted %s to %s at %x.", obj, qobject, obj_addr)
            if qobject is None:
                # Conversion failed
                continue

            if isinstance(qobject, QLinearBlock):
                for insn_addr in qobject.addr_to_insns.keys():
                    self._insaddr_to_block[insn_addr] = qobject

            # qobject.setCacheMode(QGraphicsItem.DeviceCoordinateCache)

            object_lines = int(qobject.height // self._line_height)

            if start_line >= object_lines:
                # this object should be skipped. ignore it
                start_line -= object_lines
                # adjust the offset as well
                if obj_addr <= addr < obj_addr + obj.size:
                    offset += obj_addr + obj.size - addr
                else:
                    offset += obj.size
                _l.debug("Skipping object %s (size %d). New offset: %d.", obj, obj.size, offset)
                y = -start_line * self._line_height
            else:
                if start_line > 0:
                    _l.debug("First object to paint: %s (size %d). Current offset %d. Start printing from line %d. "
                             "Y pos %d.", obj, obj.size, offset, start_line, y)
                    # this is the first object to paint
                    start_line_in_object = start_line
                    start_line = 0
                    lines += object_lines - start_line_in_object
                else:
                    lines += object_lines
                self.objects.append(qobject)
                qobject.setPos(x, y)
                scene.addItem(qobject)
                y += qobject.height + self.OBJECT_PADDING

            if lines > viewable_lines:
                break

        _l.debug("Final offset %d, start_line_in_object %d.", offset, start_line_in_object)

        # Update properties
        self._offset = offset
        self._start_line_in_object = start_line_in_object

    def _obj_to_paintable(self, obj_addr, obj):
        if isinstance(obj, Block):
            cfg_node = self.cfg.get_any_node(obj_addr, force_fastpath=True)
            func_addr = cfg_node.function_address
            if self.workspace.instance.kb.functions.contains_addr(func_addr):
                func = self.workspace.instance.kb.functions[func_addr]
                disasm = self._get_disasm(func)
                qobject = QLinearBlock(self.workspace, func_addr, self.disasm_view, disasm,
                                       self.disasm_view.infodock, obj.addr, [obj], {}, None, container=self._viewer,
                                       )
            else:
                # TODO: Get disassembly even if the function does not exist
                _l.warning("Function %s does not exist, and we cannot get disassembly for block %s.",
                           func_addr, obj)
                qobject = None
        elif isinstance(obj, MemoryData):
            qobject = QMemoryDataBlock(self.workspace, self.disasm_view.infodock, obj_addr, obj, parent=None,
                                       container=self._viewer)
        elif isinstance(obj, Unknown):
            qobject = QUnknownBlock(self.workspace, obj_addr, obj.bytes, container=self._viewer)
        else:
            qobject = None
        return qobject

    def _calculate_max_offset(self):
        try:
            max_off = next(self._offset_to_region.irange(reverse=True))
            mr = self._offset_to_region[max_off]  # type: MemoryRegion
            return max_off + mr.size
        except StopIteration:
            return 0

    def _region_from_offset(self, offset):
        try:
            off = next(self._offset_to_region.irange(maximum=offset, reverse=True))
            return off, self._offset_to_region[off]
        except StopIteration:
            return None, None

    def _addr_from_offset(self, mr, base_offset, offset):
        return mr.addr + (offset - base_offset)

    def _get_disasm(self, func):
        """

        :param func:
        :return:
        """

        if func.addr not in self._disasms:
            self._disasms[func.addr] = self.workspace.instance.project.analyses.Disassembly(function=func)
        return self._disasms[func.addr]
Пример #31
0
class UltraPage(MemoryObjectMixin, PageBase):
    SUPPORTS_CONCRETE_LOAD = True

    def __init__(self, memory=None, init_zero=False, **kwargs):
        super().__init__(**kwargs)

        if memory is not None:
            self.concrete_data = bytearray(memory.page_size)
            if init_zero:
                self.symbolic_bitmap = bytearray(memory.page_size)
            else:
                self.symbolic_bitmap = bytearray(b'\1' * memory.page_size)
        else:
            self.concrete_data = None
            self.symbolic_bitmap = None

        self.symbolic_data = SortedDict()

    @classmethod
    def new_from_shared(cls, data, memory=None, **kwargs):
        o = cls(**kwargs)
        o.concrete_data = data
        o.symbolic_bitmap = bytearray(memory.page_size)
        o.refcount = 2
        return o

    def copy(self, memo):
        o = super().copy(memo)
        o.concrete_data = bytearray(self.concrete_data)
        o.symbolic_bitmap = bytearray(self.symbolic_bitmap)
        o.symbolic_data = SortedDict(self.symbolic_data)
        return o

    def load(self,
             addr,
             size=None,
             page_addr=None,
             endness=None,
             memory=None,
             cooperate=False,
             **kwargs):
        concrete_run = []
        symbolic_run = ...
        last_run = None
        result = []

        def cycle(end):
            if last_run is symbolic_run and symbolic_run is None:
                fill(end)
            elif last_run is concrete_run:
                new_ast = claripy.BVV(concrete_run, (end - result[-1][0]) *
                                      memory.state.arch.byte_width)
                new_obj = SimMemoryObject(new_ast, result[-1][0], endness)
                result[-1] = (result[-1][0], new_obj)

        def fill(end):
            global_end_addr = end
            global_start_addr = result[-1][0]
            size = global_end_addr - global_start_addr
            new_ast = self._default_value(
                global_start_addr,
                size,
                name='%s_%x' % (memory.id, global_start_addr),
                key=(self.category, global_start_addr),
                memory=memory,
                **kwargs)
            new_item = SimMemoryObject(new_ast,
                                       global_start_addr,
                                       endness=endness)
            self.symbolic_data[global_start_addr - page_addr] = new_item
            result[-1] = (global_start_addr, new_item)

        for subaddr in range(addr, addr + size):
            realaddr = subaddr + page_addr
            if self.symbolic_bitmap[subaddr]:
                cur_val = self._get_object(subaddr, page_addr, memory=memory)
                if cur_val is last_run and last_run is symbolic_run:
                    pass
                else:
                    cycle(realaddr)
                    last_run = symbolic_run = cur_val
                    result.append((realaddr, cur_val))
            else:
                cur_val = self.concrete_data[subaddr]
                if last_run is concrete_run:
                    if endness == 'Iend_LE':
                        last_run = concrete_run = concrete_run | (
                            cur_val << (memory.state.arch.byte_width *
                                        (realaddr - result[-1][0])))
                    else:
                        last_run = concrete_run = (
                            concrete_run <<
                            memory.state.arch.byte_width) | cur_val
                    result[-1] = (result[-1][0], concrete_run)
                else:
                    cycle(realaddr)
                    last_run = concrete_run = cur_val
                    result.append((realaddr, cur_val))

        cycle(page_addr + addr + size)
        if not cooperate:
            result = self._force_load_cooperation(result,
                                                  size,
                                                  endness,
                                                  page_addr=page_addr,
                                                  memory=memory,
                                                  **kwargs)
        return result

    def store(self,
              addr,
              data: Union[int, SimMemoryObject],
              size: int = None,
              endness=None,
              memory=None,
              page_addr=None,
              cooperate=False,
              **kwargs):
        if not cooperate:
            data = self._force_store_cooperation(addr,
                                                 data,
                                                 size,
                                                 endness,
                                                 page_addr=page_addr,
                                                 memory=memory,
                                                 **kwargs)

        if size >= memory.page_size - addr:
            size = memory.page_size - addr

        if type(data) is not int:
            if data.object.op == 'BVV':
                # trim the unnecessary leading bytes if there are any
                full_bits = len(data.object)
                start = (page_addr + addr - data.base) & (
                    (1 << memory.state.arch.bits) - 1)
                if start >= data.base + data.length:
                    raise SimMemoryError("Not enough bytes to store.")
                start_bits = full_bits - start * memory.state.arch.byte_width - 1
                # trim the overflowing bytes if there are any
                end_bits = start_bits + 1 - size * memory.state.arch.byte_width
                if start_bits != full_bits or end_bits != 0:
                    if endness == 'Iend_LE':
                        start_bits, end_bits = len(
                            data.object) - end_bits - 1, len(
                                data.object) - start_bits - 1
                    obj = data.object[start_bits:end_bits]
                    data = obj.args[0]

        if type(data) is int or data.object.op == 'BVV':
            # mark range as not symbolic
            self.symbolic_bitmap[addr:addr + size] = b'\0' * size

            # store
            arange = range(addr, addr + size)
            if type(data) is int:
                ival = data
            else:  # data.object.op == 'BVV'
                ival = data.object.args[0]
            if endness == 'Iend_BE':
                arange = reversed(arange)

            assert memory.state.arch.byte_width == 8
            # TODO: Make UltraPage support architectures with greater byte_widths (but are still multiples of 8)
            for subaddr in arange:
                self.concrete_data[subaddr] = ival & 0xff
                ival >>= 8
        else:
            # mark range as symbolic
            self.symbolic_bitmap[addr:addr + size] = b'\1' * size

            # set ending object
            try:
                endpiece = next(
                    self.symbolic_data.irange(maximum=addr + size,
                                              reverse=True))
            except StopIteration:
                pass
            else:
                if endpiece != addr + size:
                    self.symbolic_data[addr +
                                       size] = self.symbolic_data[endpiece]

            # clear range
            for midpiece in self.symbolic_data.irange(maximum=addr + size - 1,
                                                      minimum=addr,
                                                      reverse=True):
                del self.symbolic_data[midpiece]

            # set.
            self.symbolic_data[addr] = data

    def merge(self,
              others: List['UltraPage'],
              merge_conditions,
              common_ancestor=None,
              page_addr: int = None,
              memory=None):

        all_pages = [self] + others
        merged_to = None
        merged_objects = set()
        merged_offsets = set()

        changed_bytes: Set[int] = set()
        for o in others:
            changed_bytes |= self.changed_bytes(o, page_addr=page_addr)

        for b in sorted(changed_bytes):
            if merged_to is not None and not b >= merged_to:
                l.info("merged_to = %d ... already merged byte 0x%x",
                       merged_to, b)
                continue
            l.debug("... on byte 0x%x", b)

            memory_objects: List[Tuple[SimMemoryObject, Any]] = []
            concretes: List[Tuple[int, Any]] = []
            unconstrained_in: List[Tuple['UltraPage', Any]] = []
            our_mo: Optional[SimMemoryObject] = None

            # first get a list of all memory objects at that location, and
            # all memories that don't have those bytes
            for pg, fv in zip(all_pages, merge_conditions):
                if pg.symbolic_bitmap[b]:
                    mo = pg._get_object(b, page_addr)
                    if mo is not None:
                        l.info("... MO present in %s", fv)
                        memory_objects.append((mo, fv))
                        if pg is self:
                            our_mo = mo
                    else:
                        l.info("... not present in %s", fv)
                        unconstrained_in.append((pg, fv))
                else:
                    # concrete data
                    concretes.append((pg.concrete_data[b], fv))

            # fast path: no memory objects, no unconstrained positions, and only one concrete value
            if not memory_objects and not unconstrained_in and len(
                    set(cv for cv, _ in concretes)) == 1:
                cv = concretes[0][0]
                self.store(b, cv, size=1, cooperate=True, page_addr=page_addr)
                continue

            # convert all concrete values into memory objects
            for cv, fv in concretes:
                mo = SimMemoryObject(claripy.BVV(cv, size=8), page_addr + b,
                                     'Iend_LE')
                memory_objects.append((mo, fv))

            mos = set(mo for mo, _ in memory_objects)
            mo_bases = set(mo.base for mo, _ in memory_objects)
            mo_lengths = set(mo.length for mo, _ in memory_objects)

            if not unconstrained_in and not (mos - merged_objects):
                continue

            # first, optimize the case where we are dealing with the same-sized memory objects
            if len(mo_bases) == 1 and len(
                    mo_lengths) == 1 and not unconstrained_in:

                to_merge = [(mo.object, fv) for mo, fv in memory_objects]

                # Update `merged_to`
                mo_base = list(mo_bases)[0]
                merged_to = mo_base + list(mo_lengths)[0]

                merged_val = self._merge_values(to_merge,
                                                memory_objects[0][0].length,
                                                memory=memory)
                if merged_val is None:
                    continue

                if our_mo is None:
                    # this object does not exist in the current page. do the store
                    new_object = SimMemoryObject(merged_val, page_addr + b,
                                                 memory_objects[0][0].endness)
                    self.store(b,
                               new_object,
                               size=list(mo_lengths)[0],
                               cooperate=True,
                               memory=memory)
                    merged_objects.add(new_object)
                else:
                    # do the replacement
                    new_object = self._replace_memory_object(our_mo,
                                                             merged_val,
                                                             memory=memory)
                    merged_objects.add(new_object)

                merged_objects.update(mos)
                merged_offsets.add(b)

            else:
                # get the size that we can merge easily. This is the minimum of
                # the size of all memory objects and unallocated spaces.
                min_size = min([
                    mo.length - (page_addr + b - mo.base)
                    for mo, _ in memory_objects
                ])
                for um, _ in unconstrained_in:
                    for i in range(0, min_size):
                        if um._contains(b + i, page_addr):
                            min_size = i
                            break
                merged_to = b + min_size
                l.info("... determined minimum size of %d", min_size)

                # Now, we have the minimum size. We'll extract/create expressions of that
                # size and merge them
                extracted = [(mo.bytes_at(page_addr + b, min_size), fv)
                             for mo, fv in memory_objects
                             ] if min_size != 0 else []
                created = [
                    (self._default_value(None,
                                         min_size,
                                         name="merge_uc_%s_%x" % (uc.id, b),
                                         memory=memory), fv)
                    for uc, fv in unconstrained_in
                ]
                to_merge = extracted + created

                merged_val = self._merge_values(to_merge,
                                                min_size,
                                                memory=memory)
                if merged_val is None:
                    continue

                self.store(b,
                           merged_val,
                           size=len(merged_val) //
                           memory.state.arch.byte_width,
                           inspect=False,
                           page_addr=page_addr,
                           memory=memory)  # do not convert endianness again

                merged_offsets.add(b)

        return merged_offsets

    def concrete_load(self, addr, size, **kwargs):
        if type(self.concrete_data) is bytearray:
            return memoryview(
                self.concrete_data)[addr:addr + size], memoryview(
                    self.symbolic_bitmap)[addr:addr + size]
        else:
            return self.concrete_data[addr:addr + size], memoryview(
                self.symbolic_bitmap)[addr:addr + size]

    def changed_bytes(self, other, page_addr=None) -> Set[int]:
        changes = set()
        for addr in range(len(self.symbolic_bitmap)):
            if self.symbolic_bitmap[addr] != other.symbolic_bitmap[addr]:
                changes.add(addr)
            elif self.symbolic_bitmap[addr] == 0:
                if self.concrete_data[addr] != other.concrete_data[addr]:
                    changes.add(addr)
            else:
                try:
                    aself = next(
                        self.symbolic_data.irange(maximum=addr, reverse=True))
                except StopIteration:
                    aself = None
                try:
                    aother = next(
                        other.symbolic_data.irange(maximum=addr, reverse=True))
                except StopIteration:
                    aother = None

                if aself is None and aother is None:
                    pass
                elif aself is None and aother is not None:
                    oobj = other.symbolic_data[aother]
                    if oobj.includes(addr + page_addr):
                        changes.add(addr)
                elif aother is None and aself is not None:
                    aobj = self.symbolic_data[aself]
                    if aobj.includes(addr + page_addr):
                        changes.add(addr)
                else:
                    real_addr = page_addr + addr
                    aobj = self.symbolic_data[aself]
                    oobj = other.symbolic_data[aother]

                    acont = aobj.includes(real_addr)
                    ocont = oobj.includes(real_addr)
                    if acont != ocont:
                        changes.add(addr)
                    elif acont is False:
                        pass
                    else:
                        abyte = aobj.bytes_at(real_addr, 1)
                        obyte = oobj.bytes_at(real_addr, 1)
                        if abyte is not obyte:
                            changes.add(addr)

        return changes

    def _contains(self, start: int, page_addr: int):
        if not self.symbolic_bitmap[start]:
            # concrete data
            return True
        else:
            # symbolic data or does not exist
            return self._get_object(start, page_addr) is not None

    def _get_object(self,
                    start: int,
                    page_addr: int,
                    memory=None) -> Optional[SimMemoryObject]:
        try:
            place = next(self.symbolic_data.irange(maximum=start,
                                                   reverse=True))
        except StopIteration:
            return None
        else:
            obj = self.symbolic_data[place]
            if obj.includes(start + page_addr):
                return obj
            elif memory is not None and obj.includes(
                    start + page_addr + (1 << memory.state.arch.bits)):
                return obj
            else:
                return None

    def replace_all_with_offsets(self,
                                 offsets: Iterable[int],
                                 old: claripy.ast.BV,
                                 new: claripy.ast.BV,
                                 memory=None):

        memory_objects = set()
        for offset in sorted(list(offsets)):
            try:
                a = next(
                    self.symbolic_data.irange(maximum=offset, reverse=True))
            except StopIteration:
                a = None

            if a is None:
                continue
            aobj = self.symbolic_data[a]
            memory_objects.add(aobj)

        replaced_objects_cache = {}
        for mo in memory_objects:
            replaced_object = None

            if mo.object in replaced_objects_cache:
                if mo.object is not replaced_objects_cache[mo.object]:
                    replaced_object = replaced_objects_cache[mo.object]

            else:
                replaced_object = mo.object.replace(old, new)
                replaced_objects_cache[mo.object] = replaced_object
                if mo.object is replaced_object:
                    # The replace does not really occur
                    replaced_object = None

            if replaced_object is not None:
                self._replace_memory_object(mo, replaced_object, memory=memory)

    def _replace_memory_object(self,
                               old: SimMemoryObject,
                               new_content: claripy.Bits,
                               memory=None):
        """
        Replaces the memory object `old` with a new memory object containing `new_content`.

        :param old:         A SimMemoryObject (i.e., one from :func:`memory_objects_for_hash()` or :func:`
                            memory_objects_for_name()`).
        :param new_content: The content (claripy expression) for the new memory object.
        :returns: the new memory object
        """

        if (old.object.size() if not old.is_bytes else len(old.object) *
                self.state.arch.byte_width) != new_content.size():
            raise SimMemoryError(
                "memory objects can only be replaced by the same length content"
            )

        new = SimMemoryObject(new_content,
                              old.base,
                              old.endness,
                              byte_width=old._byte_width)
        for k in list(self.symbolic_data):
            if self.symbolic_data[k] is old:
                self.symbolic_data[k] = new

        if isinstance(new.object, claripy.ast.BV):
            for b in range(old.base, old.base + old.length):
                self._update_mappings(b, old.object, new.object, memory=memory)

        return new
Пример #32
0
class MemoryIndex(object):
    ''' An ad-hoc memory index file for when an on-file index file does not
    exist. '''
    def __init__(self, tbl, colind):
        self.__idx = SortedDict(Key)
        init_idx_with(self, tbl, colind)

    def add(self, rowid, key):
        if key not in self.__idx:
            val = set()
            self.__idx[key] = val
        else:
            val = self.__idx[key]
        val.add(rowid)

    def clear(self):
        self.__idx.clear()

    def search(self, key, inequality):
        if inequality == '!=':
            for k in self.__idx:
                if compare(k, key, '!='):
                    yield from self.__idx[k]
        elif inequality in ('=', '=='):
            if key in self.__idx:
                yield from self.__idx[key]
        else:
            opers = {
                '<=': {
                    'minimum': None,
                    'maximum': key
                },
                '<': {
                    'minimum': None,
                    'maximum': key,
                    'inclusive': (False, False)
                },
                '>=': {
                    'minimum': key,
                    'maximum': None
                },
                '>': {
                    'minimum': key,
                    'maximum': None,
                    'inclusive': (False, False)
                }
            }
            for k in self.__idx.irange(**opers[inequality]):
                yield from self.__idx[k]

    def modify(self, old_rowid, new_rowid, key):
        if key not in self.__idx:
            return False
        s = self.__idx[key]
        if rowid not in s:
            return False
        s.remove(old_rowid)
        s.add(new_rowid)
        return True

    def delete(self, rowid, key):
        if key not in self.__idx:
            return False
        s = self.__idx[key]
        if rowid not in s:
            return False
        s.remove(rowid)
        return True
Пример #33
0
class QLinearViewer(QWidget):
    def __init__(self, workspace, disasm_view, parent=None):
        super(QLinearViewer, self).__init__(parent)

        self.workspace = workspace
        self.disasm_view = disasm_view

        self.objects = []  # Objects that will be painted

        self.cfg = None
        self.cfb = None

        self._offset_to_region = SortedDict()
        self._addr_to_region_offset = SortedDict()

        # Offset (in bytes) into the entire blanket view
        self._offset = 0
        # The maximum offset (in bytes) of the blanket view
        self._max_offset = None
        # The first line that is rendered of the first object in self.objects. Start from 0.
        self._start_line_in_object = 0

        self._linear_view = None  # type: QLinearGraphicsView
        self._disasms = {}

        self._init_widgets()

    #
    # Properties
    #

    @property
    def offset(self):
        return self._offset

    @offset.setter
    def offset(self, v):
        self._offset = v

    @property
    def start_line_in_object(self):
        return self._start_line_in_object

    @property
    def max_offset(self):
        if self._max_offset is None:
            self._max_offset = self._calculate_max_offset()
        return self._max_offset

    #
    # Proxy properties
    #

    @property
    def selected_operands(self):
        return self._linear_view.selected_operands

    @property
    def selected_insns(self):
        return self._linear_view.selected_insns

    #
    # Public methods
    #

    def initialize(self):

        if self.cfb is None:
            return

        self._addr_to_region_offset.clear()
        self._offset_to_region.clear()
        self._disasms.clear()
        self._offset = 0
        self._max_offset = None
        self._start_line_in_object = 0

        # enumerate memory regions
        byte_offset = 0
        for mr in self.cfb.regions:  # type:MemoryRegion
            self._addr_to_region_offset[mr.addr] = byte_offset
            self._offset_to_region[byte_offset] = mr
            byte_offset += mr.size

    def navigate_to_addr(self, addr):
        if not self._addr_to_region_offset:
            return
        try:
            floor_region_addr = next(
                self._addr_to_region_offset.irange(maximum=addr, reverse=True))
        except StopIteration:
            floor_region_addr = next(self._addr_to_region_offset.irange())
        floor_region_offset = self._addr_to_region_offset[floor_region_addr]

        offset_into_region = addr - floor_region_addr
        self.navigate_to(floor_region_offset + offset_into_region)

    def refresh(self):
        self._linear_view.refresh()

    def navigate_to(self, offset):

        self._linear_view.navigate_to(int(offset))

        self.prepare_objects(offset)

        self._linear_view.refresh()

    def prepare_objects(self, offset, start_line=0):
        """
        Prepare objects to print based on offset and start_line. Update self.objects, self._offset, and
        self._start_line_in_object.

        :param int offset:      Beginning offset (in bytes) to display in the linear viewer.
        :param int start_line:  The first line into the first object to display in the linear viewer.
        :return:                None
        """

        if offset == self._offset and start_line == self._start_line_in_object:
            return

        # Convert the offset to memory region
        base_offset, mr = self._region_from_offset(
            offset)  # type: int,MemoryRegion
        if mr is None:
            return

        addr = self._addr_from_offset(mr, base_offset, offset)
        _l.debug("Address %#x, offset %d, start_line %d.", addr, offset,
                 start_line)

        if start_line < 0:
            # Which object are we currently displaying at the top of the disassembly view?
            try:
                top_obj_addr = self.cfb.floor_addr(addr=addr)
            except KeyError:
                top_obj_addr = addr

            # Reverse-iterate until we have enough lines to compensate start_line
            for obj_addr, obj in self.cfb.ceiling_items(addr=top_obj_addr,
                                                        reverse=True,
                                                        include_first=False):
                qobject = self._obj_to_paintable(obj_addr, obj)
                if qobject is None:
                    continue
                object_lines = int(qobject.height //
                                   self._linear_view.line_height())
                _l.debug(
                    "Compensating negative start_line: object %s, object_lines %d.",
                    obj, object_lines)
                start_line += object_lines
                if start_line >= 0:
                    addr = obj_addr
                    # Update offset
                    new_region_addr = next(
                        self._addr_to_region_offset.irange(maximum=addr,
                                                           reverse=True))
                    new_region_offset = self._addr_to_region_offset[
                        new_region_addr]
                    offset = (addr - new_region_addr) + new_region_offset
                    break
            else:
                # umm we don't have enough objects to compensate the negative start_line
                start_line = 0
                # update addr and offset to their minimal values
                addr = next(self._addr_to_region_offset.irange())
                offset = self._addr_to_region_offset[addr]

        _l.debug("After adjustment: Address %#x, offset %d, start_line %d.",
                 addr, offset, start_line)

        self.objects = []

        viewable_lines = int(self._linear_view.height() //
                             self._linear_view.line_height())
        lines = 0
        start_line_in_object = 0

        # Load a page of objects
        for obj_addr, obj in self.cfb.floor_items(addr=addr):
            qobject = self._obj_to_paintable(obj_addr, obj)
            if qobject is None:
                # Conversion failed
                continue

            if isinstance(qobject, QBlock):
                for insn_addr in qobject.addr_to_insns.keys():
                    self._linear_view._add_insn_addr_block_mapping(
                        insn_addr, qobject)

            object_lines = int(qobject.height //
                               self._linear_view.line_height())

            if start_line >= object_lines:
                # this object should be skipped. ignore it
                start_line -= object_lines
                # adjust the offset as well
                if obj_addr <= addr < obj_addr + obj.size:
                    offset += obj_addr + obj.size - addr
                else:
                    offset += obj.size
                _l.debug("Skipping object %s (size %d). New offset: %d.", obj,
                         obj.size, offset)
            else:
                if start_line > 0:
                    _l.debug(
                        "First object to paint: %s (size %d). Current offset %d.",
                        obj, obj.size, offset)
                    # this is the first object to paint
                    start_line_in_object = start_line
                    start_line = 0
                    lines += object_lines - start_line_in_object
                else:
                    lines += object_lines
                self.objects.append(qobject)

            if lines > viewable_lines:
                break

        _l.debug("Final offset %d, start_line_in_object %d.", offset,
                 start_line_in_object)

        # Update properties
        self._offset = offset
        self._start_line_in_object = start_line_in_object

    #
    # Private methods
    #

    def _init_widgets(self):

        self._linear_view = QLinearGraphicsView(self, self.disasm_view)

        layout = QHBoxLayout()
        layout.addWidget(self._linear_view)
        layout.setContentsMargins(0, 0, 0, 0)

        self.setLayout(layout)

        # Setup proxy methods
        self.update_label = self._linear_view.update_label
        self.select_instruction = self._linear_view.select_instruction
        self.unselect_instruction = self._linear_view.unselect_instruction
        self.unselect_all_instructions = self._linear_view.unselect_all_instructions
        self.select_operand = self._linear_view.select_operand
        self.unselect_operand = self._linear_view.unselect_operand
        self.unselect_all_operands = self._linear_view.unselect_all_operands
        self.show_selected = self._linear_view.show_selected
        self.show_instruction = self._linear_view.show_instruction

    def _obj_to_paintable(self, obj_addr, obj):
        if isinstance(obj, Block):
            cfg_node = self.cfg.get_any_node(obj.addr, force_fastpath=True)
            if cfg_node is not None:
                func_addr = cfg_node.function_address
                func = self.cfg.kb.functions[func_addr]  # FIXME: Resiliency
                disasm = self._get_disasm(func)
                qobject = QBlock(
                    self.workspace,
                    func_addr,
                    self.disasm_view,
                    disasm,
                    self.disasm_view.infodock,
                    obj.addr,
                    [obj],
                    {},
                    mode='linear',
                )
            else:
                # TODO: This should be displayed as a function thunk
                _l.error(
                    "QLinearViewer: Unexpected result: CFGNode %#x is not found in CFG."
                    "Display it as a QUnknownBlock.", obj.addr)
                qobject = QUnknownBlock(self.workspace, obj_addr, obj.bytes)

        elif isinstance(obj, Unknown):
            qobject = QUnknownBlock(self.workspace, obj_addr, obj.bytes)

        else:
            qobject = None

        return qobject

    def _calculate_max_offset(self):
        try:
            max_off = next(self._offset_to_region.irange(reverse=True))
            mr = self._offset_to_region[max_off]  # type: MemoryRegion
            return max_off + mr.size
        except StopIteration:
            return 0

    def _region_from_offset(self, offset):
        try:
            off = next(
                self._offset_to_region.irange(maximum=offset, reverse=True))
            return off, self._offset_to_region[off]
        except StopIteration:
            return None, None

    def _addr_from_offset(self, mr, base_offset, offset):
        return mr.addr + (offset - base_offset)

    def _get_disasm(self, func):
        """

        :param func:
        :return:
        """

        if func.addr not in self._disasms:
            self._disasms[
                func.
                addr] = self.workspace.instance.project.analyses.Disassembly(
                    function=func)
        return self._disasms[func.addr]
Пример #34
0
class KeyedRegion(object):
    """
    KeyedRegion keeps a mapping between stack offsets and all objects covering that offset. It assumes no variable in
    this region overlap with another variable in this region.

    Registers and function frames can all be viewed as a keyed region.
    """
    def __init__(self, tree=None):
        self._storage = SortedDict() if tree is None else tree

    def _get_container(self, offset):
        try:
            base_offset = next(self._storage.irange(maximum=offset, reverse=True))
        except StopIteration:
            return offset, None
        else:
            container = self._storage[base_offset]
            if container.includes(offset):
                return base_offset, container
            return offset, None

    def __contains__(self, offset):
        """
        Test if there is at least one varaible covering the given offset.

        :param offset:
        :return:
        """

        return self._get_container(offset)[1] is not None

    def __len__(self):
        return len(self._storage)

    def __iter__(self):
        return iter(self._storage.values())

    def __eq__(self, other):
        if set(self._storage.keys()) != set(other._storage.keys()):
            return False

        for k, v in self._storage.items():
            if v != other._storage[k]:
                return False

        return True

    def copy(self):
        if not self._storage:
            return KeyedRegion()

        kr = KeyedRegion()
        for key, ro in self._storage.items():
            kr._storage[key] = ro.copy()
        return kr

    def merge(self, other, make_phi_func=None):
        """
        Merge another KeyedRegion into this KeyedRegion.

        :param KeyedRegion other: The other instance to merge with.
        :return: None
        """

        # TODO: is the current solution not optimal enough?
        for _, item in other._storage.items():  # type: RegionObject
            for loc_and_var in item.stored_objects:
                self.__store(loc_and_var, overwrite=False, make_phi_func=make_phi_func)

        return self

    def dbg_repr(self):
        """
        Get a debugging representation of this keyed region.
        :return: A string of debugging output.
        """
        keys = self._storage.keys()
        offset_to_vars = { }

        for key in sorted(keys):
            ro = self._storage[key]
            variables = [ obj.obj for obj in ro.stored_objects ]
            offset_to_vars[key] = variables

        s = [ ]
        for offset, variables in offset_to_vars.items():
            s.append("Offset %#x: %s" % (offset, variables))
        return "\n".join(s)

    def add_variable(self, start, variable):
        """
        Add a variable to this region at the given offset.

        :param int start:
        :param SimVariable variable:
        :return: None
        """

        size = variable.size if variable.size is not None else 1

        self.add_object(start, variable, size)

    def add_object(self, start, obj, object_size):
        """
        Add/Store an object to this region at the given offset.

        :param start:
        :param obj:
        :param int object_size: Size of the object
        :return:
        """

        self._store(start, obj, object_size, overwrite=False)

    def set_variable(self, start, variable):
        """
        Add a variable to this region at the given offset, and remove all other variables that are fully covered by
        this variable.

        :param int start:
        :param SimVariable variable:
        :return: None
        """

        size = variable.size if variable.size is not None else 1

        self.set_object(start, variable, size)

    def set_object(self, start, obj, object_size):
        """
        Add an object to this region at the given offset, and remove all other objects that are fully covered by this
        object.

        :param start:
        :param obj:
        :param object_size:
        :return:
        """

        self._store(start, obj, object_size, overwrite=True)

    def get_base_addr(self, addr):
        """
        Get the base offset (the key we are using to index objects covering the given offset) of a specific offset.

        :param int addr:
        :return:
        :rtype:  int or None
        """

        base_addr, container = self._get_container(addr)
        if container is None:
            return None
        else:
            return base_addr

    def get_variables_by_offset(self, start):
        """
        Find variables covering the given region offset.

        :param int start:
        :return: A list of stack variables.
        :rtype:  set
        """

        _, container = self._get_container(start)
        if container is None:
            return []
        else:
            return container.internal_objects

    def get_objects_by_offset(self, start):
        """
        Find objects covering the given region offset.

        :param start:
        :return:
        """

        _, container = self._get_container(start)
        if container is None:
            return set()
        else:
            return container.internal_objects

    #
    # Private methods
    #

    def _store(self, start, obj, size, overwrite=False):
        """
        Store a variable into the storage.

        :param int start: The beginning address of the variable.
        :param obj: The object to store.
        :param int size: Size of the object to store.
        :param bool overwrite: Whether existing objects should be overwritten or not.
        :return: None
        """

        stored_object = StoredObject(start, obj, size)
        self.__store(stored_object, overwrite=overwrite)

    def __store(self, stored_object, overwrite=False, make_phi_func=None):
        """
        Store a variable into the storage.

        :param StoredObject stored_object: The descriptor describing start address and the variable.
        :param bool overwrite: Whether existing objects should be overwritten or not.
        :return: None
        """

        start = stored_object.start
        object_size = stored_object.size
        end = start + object_size

        # region items in the middle
        overlapping_items = list(self._storage.irange(start, end-1))

        # is there a region item that begins before the start and overlaps with this variable?
        floor_key, floor_item = self._get_container(start)
        if floor_item is not None and floor_key not in overlapping_items:
                # insert it into the beginning
                overlapping_items.insert(0, floor_key)

        # scan through the entire list of region items, split existing regions and insert new regions as needed
        to_update = {start: RegionObject(start, object_size, {stored_object})}
        last_end = start

        for floor_key in overlapping_items:
            item = self._storage[floor_key]
            if item.start < start:
                # we need to break this item into two
                a, b = item.split(start)
                if overwrite:
                    b.set_object(stored_object)
                else:
                    self._add_object_or_make_phi(b, stored_object, make_phi_func=make_phi_func)
                to_update[a.start] = a
                to_update[b.start] = b
                last_end = b.end
            elif item.start > last_end:
                # there is a gap between the last item and the current item
                # fill in the gap
                new_item = RegionObject(last_end, item.start - last_end, {stored_object})
                to_update[new_item.start] = new_item
                last_end = new_item.end
            elif item.end > end:
                # we need to split this item into two
                a, b = item.split(end)
                if overwrite:
                    a.set_object(stored_object)
                else:
                    self._add_object_or_make_phi(a, stored_object, make_phi_func=make_phi_func)
                to_update[a.start] = a
                to_update[b.start] = b
                last_end = b.end
            else:
                if overwrite:
                    item.set_object(stored_object)
                else:
                    self._add_object_or_make_phi(item, stored_object, make_phi_func=make_phi_func)
                to_update[item.start] = item

        self._storage.update(to_update)

    def _is_overlapping(self, start, variable):

        if variable.size is not None:
            # make sure this variable does not overlap with any other variable
            end = start + variable.size
            try:
                prev_offset = next(self._storage.irange(maximum=end-1, reverse=True))
            except StopIteration:
                prev_offset = None

            if prev_offset is not None:
                if start <= prev_offset < end:
                    return True
                prev_item = self._storage[prev_offset][0]
                prev_item_size = prev_item.size if prev_item.size is not None else 1
                if start < prev_offset + prev_item_size < end:
                    return True
        else:
            try:
                prev_offset = next(self._storage.irange(maximum=start, reverse=True))
            except StopIteration:
                prev_offset = None

            if prev_offset is not None:
                prev_item = self._storage[prev_offset][0]
                prev_item_size = prev_item.size if prev_item.size is not None else 1
                if prev_offset <= start < prev_offset + prev_item_size:
                    return True

        return False

    def _add_object_or_make_phi(self, item, stored_object, make_phi_func=None):  #pylint:disable=no-self-use
        if not make_phi_func or len({stored_object.obj} | item.internal_objects) == 1:
            item.add_object(stored_object)
        else:
            # make a phi node
            item.set_object(StoredObject(stored_object.start,
                                         make_phi_func(stored_object.obj, *item.internal_objects),
                                         stored_object.size,
                                         )
                            )
Пример #35
0
class PatchManager(KnowledgeBasePlugin):
    """
    A placeholder-style implementation for a binary patch manager. This class should be significantly changed in the
    future when all data about loaded binary objects are loaded into angr knowledge base from CLE. As of now, it only
    stores byte-level replacements. Other angr components may choose to use or not use information provided by this
    manager. In other words, it is not transparent.

    Patches should not overlap, but it's user's responsibility to check for and avoid overlapping patches.
    """
    def __init__(self, kb):
        super().__init__()

        self._patches = SortedDict()
        self._kb = kb

    def add_patch(self, addr, new_bytes):
        self._patches[addr] = Patch(addr, new_bytes)

    def remove_patch(self, addr):
        if addr in self._patches:
            del self._patches[addr]

    def patch_addrs(self):
        return self._patches.keys()

    def get_patch(self, addr):
        """
        Get patch at the given address.

        :param int addr:    The address of the patch.
        :return:            The patch if there is one starting at the address, or None if there isn't any.
        :rtype:             Patch or None
        """
        return self._patches.get(addr, None)

    def get_all_patches(self, addr, size):
        """
        Retrieve all patches that cover a region specified by [addr, addr+size).

        :param int addr:    The address of the beginning of the region.
        :param int size:    Size of the region.
        :return:            A list of patches.
        :rtype:             list
        """
        patches = []
        for patch_addr in self._patches.irange(maximum=addr + size - 1,
                                               reverse=True):
            p = self._patches[patch_addr]
            if self.overlap(p.addr, p.addr + len(p), addr, addr + size):
                patches.append(p)
            else:
                break
        return patches[::-1]

    def keys(self):
        return self._patches.keys()

    def items(self):
        return self._patches.items()

    def values(self):
        return self._patches.values()

    def copy(self):
        o = PatchManager(self._kb)
        o._patches = self._patches.copy()

    @staticmethod
    def overlap(a0, a1, b0, b1):
        return a0 <= b0 < a1 or a0 <= b1 < a1 or b0 <= a0 < b1
Пример #36
0
class LevelTrace(object):
    """ Traces the level of some entity across a time span """

    def __init__(self, trace=None):
        """ Creates a new level trace, possibly copying from an existing object. """

        if trace is None:
            self._trace = SortedDict()
        elif isinstance(trace, LevelTrace):
            self._trace = SortedDict(trace._trace)
        else:
            self._trace = SortedDict(trace)

        # Make sure trace is terminated (returns to 0)
        if len(self._trace) > 0 and self._trace[self._trace.keys()[-1]] != 0:
            raise ValueError(
                "Trace not terminated - ends with {}:{}!".format(
                    self._trace.keys()[-1], self._trace[self._trace.keys()[-1]])
                )

    def __repr__(self):
        items = ', '.join(["{!r}: {!r}".format(k, v)
                           for k, v in self._trace.items()])
        return "LevelTrace({{{}}})".format(items)

    def __eq__(self, other):
        return self._trace == other._trace

    def __neg__(self):
        return self.map(operator.neg)
    def __sub__(self, other):
        return self.zip_with(other, operator.sub)
    def __add__(self, other):
        return self.zip_with(other, operator.add)

    def start(self):
        """ Returns first non-null point in trace """
        if len(self._trace) == 0:
            return 0
        return self._trace.keys()[0]

    def end(self):
        """ Returns first point in trace that is null and only followed by nulls """
        if len(self._trace) == 0:
            return 0
        return self._trace.keys()[-1]

    def length(self):
        if len(self._trace) == 0:
            return 0
        return self.end() - self.start()

    def get(self, time):
        ix = self._trace.bisect_right(time) - 1
        if ix < 0:
            return 0
        else:
            (_, lvl) = self._trace.peekitem(ix)
            return lvl

    def map(self, fn):
        return LevelTrace({t: fn(v) for t, v in self._trace.items()})
    def map_key(self, fn):
        return LevelTrace(dict(fn(t, v) for t, v in self._trace.items()))

    def shift(self, time):
        return self.map_key(lambda t, v: (t + time, v))

    def __getitem__(self, where):

        # For non-slices defaults to get
        if not isinstance(where, slice):
            return self.get(where)
        if where.step is not None:
            raise ValueError("Stepping meaningless for LevelTrace!")

        # Limit
        res = LevelTrace(self)
        if where.start is not None and where.start > res.start():
            res.set(res.start(), where.start, 0)
        if where.stop is not None and where.stop < res.end():
            res.set(where.stop, res.end(), 0)

        # Shift, if necessary
        if where.start is not None:
            res = res.shift(-where.start)
        return res

    def set(self, start, end, level):
        """ Sets the level for some time range
        :param start: Start of range
        :param end: End of range
        :aram amount: Level to set
        """

        # Check errors, no-ops
        if start >= end:
            return

        # Determine levels at start (and before start)
        start_ix = self._trace.bisect_right(start) - 1
        prev_lvl = lvl = 0
        if start_ix >= 0:
            (t, lvl) = self._trace.peekitem(start_ix)
            # If we have no entry exactly at our start point, the
            # level was constant at this point before
            if start > t:
                prev_lvl = lvl
            # Otherwise look up previous level. Default 0 (see above)
            elif start_ix > 0:
                (_, prev_lvl) = self._trace.peekitem(start_ix-1)

        # Prepare start
        if prev_lvl == level:
            if start in self._trace:
                del self._trace[start]
        else:
            self._trace[start] = level

        # Remove all in-between states
        for time in list(self._trace.irange(start, end, inclusive=(False, False))):
            lvl = self._trace[time]
            del self._trace[time]

        # Add or remove end, if necessary
        if end not in self._trace:
            if lvl != level:
                self._trace[end] = lvl
        elif level == self._trace[end]:
            del self._trace[end]


    def add(self, start, end, amount):
        """ Increases the level for some time range
        :param start: Start of range
        :param end: End of range
        :aram amount: Amount to add to level
        """

        # Check errors, no-ops
        if start > end:
            raise ValueError("End needs to be after start!")
        if start == end or amount == 0:
            return

        # Determine levels at start (and before start)
        start_ix = self._trace.bisect_right(start) - 1
        prev_lvl = lvl = 0
        if start_ix >= 0:
            (t, lvl) = self._trace.peekitem(start_ix)
            # If we have no entry exactly at our start point, the
            # level was constant at this point before
            if start > t:
                prev_lvl = lvl
            # Otherwise look up previous level. Default 0 (see above)
            elif start_ix > 0:
                (_, prev_lvl) = self._trace.peekitem(start_ix-1)

        # Prepare start
        if prev_lvl == lvl + amount:
            del self._trace[start]
        else:
            self._trace[start] = lvl + amount

        # Update all in-between states
        for time in self._trace.irange(start, end, inclusive=(False, False)):
            lvl = self._trace[time]
            self._trace[time] = lvl + amount

        # Add or remove end, if necessary
        if end not in self._trace:
            self._trace[end] = lvl
        elif lvl + amount == self._trace[end]:
            del self._trace[end]

    def __delitem__(self, where):

        # Cannot set single values
        if not isinstance(where, slice):
            raise ValueError("Cannot set level for single point, pass an interval!")
        if where.step is not None:
            raise ValueError("Stepping meaningless for LevelTrace!")

        # Set range to zero
        start = (where.start if where.start is not None else self.start())
        end = (where.stop if where.stop is not None else self.end())
        self.set(start, end, 0)

    def __setitem__(self, where, value):

        # Cannot set single values
        if not isinstance(where, slice):
            raise ValueError("Cannot set level for single point, pass an interval!")
        if where.step is not None:
            raise ValueError("Stepping meaningless for LevelTrace!")

        # Setting a level trace?
        if isinstance(value, LevelTrace):

            # Remove existing data
            del self[where]
            if where.start is not None:
                if value.start() < 0:
                    raise ValueError("Level trace starts before 0!")
                value = value.shift(where.start)
            if where.stop is not None:
                if value.end() > where.stop:
                    raise ValueError("Level trace to set is larger than slice!")
            self._trace = (self + value)._trace

        else:

            # Otherwise set constant value
            start = (where.start if where.start is not None else self.start())
            end = (where.stop if where.stop is not None else self.end())
            self.set(start, end, value)

    def foldl1(self, start, end, fn):
        """
        Does a left-fold over the levels present in the given range. Seeds
        with level at start.
        """

        if start > end:
            raise ValueError("End needs to be after start!")
        val = self.get(start)
        start_ix = self._trace.bisect_right(start)
        end_ix = self._trace.bisect_left(end)
        for lvl in self._trace.values()[start_ix:end_ix]:
            val = fn(val, lvl)
        return val

    def minimum(self, start, end):
        """ Returns the lowest level in the given range """
        return self.foldl1(start, end, min)
    def maximum(self, start, end):
        """ Returns the highest level in the given range """
        return self.foldl1(start, end, max)

    def foldl_time(self, start, end, val, fn):
        """
        Does a left-fold over the levels present in the given range,
        also passing how long the level was held. Seed passed.
        """

        if start > end:
            raise ValueError("End needs to be after start!")

        last_time = start
        last_lvl = self.get(start)

        start_ix = self._trace.bisect_right(start)
        end_ix = self._trace.bisect_left(end)
        for time, lvl in self._trace.items()[start_ix:end_ix]:
            val = fn(val, time-last_time, last_lvl)
            last_time = time
            last_lvl = lvl

        return fn(val, end-last_time, last_lvl)

    def integrate(self, start, end):
        """ Returns the integral over a range (sum below level curve) """
        return self.foldl_time(start, end, 0,
                               lambda v, time, lvl: v + time * lvl)
    def average(self, start, end):
        """ Returns the average level over a given range """
        return self.integrate(start, end) / (end - start)

    def find_above(self, time, level):
        """Returns the first time larger or equal to the given start time
        where the level is at least the specified value.
        """

        if self.get(time) >= level:
            return time
        ix = self._trace.bisect_right(time)
        for t, lvl in self._trace.items()[ix:]:
            if lvl >= level:
                return t
        return None

    def find_below(self, time, level):
        """Returns the first time larger or equal to the given start time
        where the level is less or equal the specified value.
        """

        if self.get(time) <= level:
            return time
        ix = self._trace.bisect_right(time)
        for t, lvl in self._trace.items()[ix:]:
            if lvl <= level:
                return t
        return None

    def find_below_backward(self, time, level):
        """Returns the last time smaller or equal to the given time where
        there exists a region to the left where the level is below the
        given value.
        """

        last = time
        ix = self._trace.bisect_right(time)-1
        if ix >= 0:
            for t, lvl in self._trace.items()[ix::-1]:
                if lvl <= level and time > t:
                    return last
                last = t
        if level >= 0:
            return last
        return None

    def find_above_backward(self, time, level):
        """Returns the last time smaller or equal to the given time where
        there exists a region to the left where the level is below the
        given value.
        """

        last = time
        ix = self._trace.bisect_right(time)-1
        if ix >= 0:
            for t, lvl in self._trace.items()[ix::-1]:
                if lvl >= level and time > t:
                    return last
                last = t
        if level <= 0:
            return last
        return None

    def find_period_below(self, start, end, target, length):
        """Returns a period where the level is below the target for a certain
        length of time, within a given start and end time"""

        if start > end:
            raise ValueError("End needs to be after start!")
        if length < 0:
            raise ValueError("Period length must be larger than zero!")

        period_start = (start if self.get(start) <= target else None)

        start_ix = self._trace.bisect_right(start)
        end_ix = self._trace.bisect_left(end)
        for time, lvl in self._trace.items()[start_ix:end_ix]:
            # Period long enough?
            if period_start is not None:
                if time >= period_start + length:
                    return period_start
            # Not enough space until end?
            elif time + length > end:
               return None
            # Above target? Reset period
            if lvl > target:
                period_start = None
            else:
                if period_start is None:
                    period_start = time

        # Possible at end?
        if period_start is not None and period_start+length <= end:
            return period_start

        # Nothing found
        return None

    def zip_with(self, other, fn):

        # Simple cases
        if len(self._trace) == 0:
            return other.map(lambda x: fn(0, x))
        if len(other._trace) == 0:
            return self.map(lambda x: fn(x, 0))

        # Read first item from both sides
        left = self._trace.items()
        right = other._trace.items()
        left_ix = 0
        right_ix = 0
        left_val = 0
        right_val = 0
        last_val = 0

        trace = SortedDict()

        # Go through pairs
        while left_ix < len(left) and right_ix < len(right):

            # Next items
            lt,lv = left[left_ix]
            rt,rv = right[right_ix]

            # Determine what to do
            if lt < rt:
                v = fn(lv, right_val)
                if v != last_val:
                    last_val = trace[lt] = v
                left_val = lv
                left_ix += 1
            elif lt > rt:
                v = fn(left_val, rv)
                if v != last_val:
                    last_val = trace[rt] = v
                right_val = rv
                right_ix += 1
            else:
                v = fn(lv, rv)
                if v != last_val:
                    last_val = trace[lt] = v
                left_val = lv
                left_ix += 1
                right_val = rv
                right_ix += 1

        # Handle left-overs
        while left_ix < len(left):
            lt,lv = left[left_ix]
            v = fn(lv, right_val)
            if v != last_val:
                last_val = trace[lt] = v
            left_ix += 1
        while right_ix < len(right):
            rt,rv = right[right_ix]
            v = fn(left_val, rv)
            if v != last_val:
                last_val = trace[rt] = v
            right_ix += 1

        return LevelTrace(trace)