# -*- coding: utf-8 -*-
# This code is part of Amoco
# Copyright (C) 2006-2011 Axel Tillequin (bdcht3@gmail.com)
# published under GPLv2 license
"""
code.py
=======
This module defines classes that represent assembly instructions blocks,
functions, and calls to *external* functions. In amoco, such objects are
found as :attr:`node.data` in nodes of a :class:`cfg.graph`. As such,they
all provide a common API with:
* ``address`` to identify and locate the object in memory
* ``support`` to get the address range of the object
* ``view`` to display the object
"""
from heapq import heappush
from amoco.logger import Log
logger = Log(__name__)
logger.debug("loading module")
from amoco.ui.views import blockView, funcView
# -------------------------------------------------------------------------------
class acode(object):
_is_block = False
_is_func = False
def cut(self, address):
raise ValueError(address)
[docs]class block(acode):
"""A block instance holds a sequence of instructions.
Args:
instr (list[instruction]): the sequence of continuous (ordered) instructions
Attributes:
instr (list): the list of instructions of the block.
view (:class:`blockView`): holds the :mod:`ui.views` object used to display the block.
length (int): the byte length of the block instructions sequence.
support (tuple): the memory footprint of the block
"""
_is_block = True
__slots__ = ["instr", "view"]
def __init__(self, instrlist):
self.instr = instrlist
self.view = blockView(self)
@property
def address(self):
"""address (:class:`cst`): the address of the first instruction in the block.
"""
try:
return self.instr[0].address
except IndexError:
return None
@property
def length(self):
return sum([i.length for i in self.instr], 0)
def __len__(self):
return self.length
# @property
# def cfi(self):
# ii = self.instr
# TODO
@property
def support(self):
if len(self.instr) > 0:
return (self.address, self.address + self.length)
else:
return (None, None)
def __getitem__(self, i):
"""block objects support slicing from given start/stop addresses
Args:
i (slice): start and stop address *within* the block. The
values must match addresses of instructions otherwise
a :exc:`ValueError` exception is raised.
Returns:
block: a new block with selected instructions.
"""
sta, sto, stp = i.indices(self.length)
assert stp == 1
pos = [0]
for i in self.instr:
pos.append(pos[-1] + i.length)
try:
ista = pos.index(sta)
isto = pos.index(sto)
except ValueError:
logger.warning(
"can't slice block: indices must match instruction boudaries"
)
return None
I = self.instr[ista:isto]
if len(I) > 0:
return block(self.instr[ista:isto])
[docs] def cut(self, address):
"""cutting the block at given address will remove instructions after this address,
(which needs to be aligned with instructions boundaries.) The effect is thus to
reduce the block size.
Args:
address (cst): the address where the cut occurs.
Returns:
int: the number of instructions removed from the block.
"""
I = [i.address for i in self.instr]
try:
pos = I.index(address)
except ValueError:
logger.warning(
"invalid attempt to cut block @%s at %s" % (self.address, address)
)
return 0
else:
self.instr = self.instr[:pos]
nl = len(I) - pos
return nl
def __str__(self):
T = self.view._vltable(formatter="Null")
return "\n".join([r.show(raw=True, **T.rowparams) for r in T.rows])
def __repr__(self):
sta, sto = self.support
nbi = len(self.instr)
return "<{} object ({}-{}) with {} instructions>".format(
self.__class__.__name__, sta, sto, nbi
)
[docs] def raw(self):
"""returns the *raw* bytestring of the block instructions.
"""
return b"".join([i.bytes for i in self.instr])
def __cmp__(self, b):
return cmp(self.raw(), b.raw())
def __eq__(self, b):
return self.raw() == b.raw()
def __hash__(self):
return hash(self.address.value)
def __getstate__(self):
return self.instr
def __setstate__(self, state):
self.instr = state
self.view = blockView(self)
# ------------------------------------------------------------------------------
[docs]class func(acode):
"""A graph of blocks that represents a function's Control-Flow-Graph (CFG).
Args:
g (graph_core): the connected graph component of nodes.
Attributes:
cfg (graph_core): the :class:`graph_core` CFG of the function
(see :mod:`cfg`.)
blocks (list[block]): the list of blocks in the CFG
support (tuple): the memory footprint of the function
"""
_is_func = True
__slots__ = ["cfg", "view"]
# the init of a func takes a core_graph and creates a map of it:
def __init__(self, g=None):
self.cfg = g
if self.cfg:
roots = self.cfg.roots()
if len(roots) > 1:
raise ValueError("multiple roots node in CFG")
self.view = funcView(self)
@property
def address(self):
root = self.cfg.roots()[0]
return root.data.address
@property
def blocks(self):
"""blocks (list): the list of blocks within the function.
"""
return sorted(
filter(lambda x: x._is_block, [n.data for n in self.cfg.sV]),
key=lambda x: x.address,
)
@property
def support(self):
smin = self.address
smax = max((b.address + b.length for b in self.blocks))
return (smin, smax)
def __str__(self):
return "%s{%d}" % (self.address, len(self.blocks))
def __getstate__(self):
return self.cfg
def __setstate__(self, state):
self.cfg = state
self.view = funcView(self)
# ------------------------------------------------------------------------------
[docs]class tag:
"""defines keys as class attributes, used in :attr:`misc` attributes to
indicate various relevant properties of blocks within functions.
"""
FUNC_START = "func_start"
FUNC_END = "func_end"
FUNC_STACK = "func_stack"
FUNC_UNSTACK = "func_unstack"
FUNC_CALL = "func_call"
FUNC_GOTO = "func_goto"
FUNC_ARG = "func_arg"
FUNC_VAR = "func_var"
FUNC_IN = "func_in"
FUNC_OUT = "func_out"
LOOP_START = "loop_start"
LOOP_END = "loop_end"
LOOP_COND = "loop_cond"
[docs] @classmethod
def list(cls):
"""get the list of all defined keys
"""
return filter(lambda kv: kv[0].startswith("FUNC_"), cls.__dict__.items())
[docs] @classmethod
def sig(cls, name):
"""symbols for tag keys used to compute the block's signature
"""
return {
"cond": "?",
"func": "F",
cls.FUNC_START: "e",
cls.FUNC_END: "r",
cls.FUNC_STACK: "+",
cls.FUNC_UNSTACK: "-",
cls.FUNC_CALL: "c",
cls.FUNC_GOTO: "j",
cls.FUNC_ARG: "a",
cls.FUNC_VAR: "v",
cls.FUNC_IN: "i",
cls.FUNC_OUT: "o",
cls.LOOP_START: "l",
}.get(name, "")
def _code_misc_default():
return 0