From ebd59e64a57e18247daff8584ecf18efa6bddbea Mon Sep 17 00:00:00 2001 From: Axel Tillequin Date: Fri, 13 Nov 2015 14:35:33 +0100 Subject: [PATCH] squash merge of develop branch: - improve itercfg method and lbackward CFG reconstruction - improve widening/fixpoint in computation of func's map - improve pretty printing methods - start merging ui.graphics packages --- README.rst | 17 +- amoco/arch/arm/v7/formats.py | 26 +-- amoco/arch/core.py | 29 +-- amoco/arch/sparc/asm.py | 10 +- amoco/arch/sparc/formats.py | 2 +- amoco/arch/x64/spec_ia32e.py | 6 +- amoco/arch/x86/cpu_x86.py | 10 +- amoco/arch/x86/env.py | 35 ++-- amoco/arch/x86/formats.py | 146 ++++++++++----- amoco/arch/x86/spec_ia32.py | 4 + amoco/cas/expressions.py | 227 ++++++++++++++++------- amoco/cas/mapper.py | 64 ++++--- amoco/cas/smt.py | 89 ++++----- amoco/cfg.py | 2 + amoco/code.py | 213 +++++++++++----------- amoco/config.py | 12 +- amoco/main.py | 162 ++++++++--------- amoco/system/core.py | 29 +-- amoco/ui/graphics/__init__.py | 3 + amoco/ui/graphics/gtk_/__init__.py | 0 amoco/ui/graphics/kivy_/__init__.py | 0 amoco/ui/graphics/qt_/__init__.py | 0 amoco/ui/graphics/term.py | 3 + amoco/ui/render.py | 231 +++++++++++++++++++++++- amoco/ui/signals.py | 11 ++ amoco/ui/views.py | 31 ++++ doc/conf.py | 269 ++++++++++++++++++++++++++++ doc/index.rst | 22 +++ tests/conftest.py | 11 ++ tests/samples/arm/helloworld.s | 11 ++ tests/samples/arm/hw | Bin 0 -> 33470 bytes tests/samples/sparc/saverestore | Bin 0 -> 66569 bytes tests/samples/sparc/saverestore.s | 18 ++ tests/samples/x86/loop_simple.elf | Bin tests/samples/x86/prefixes.elf | Bin tests/test_arch_x86.py | 5 +- tests/test_cas_exp.py | 28 +++ tests/test_cas_smt.py | 3 + tests/test_main_target.py | 41 +++++ tests/test_system_core.py | 14 +- tests/test_ui_render.py | 27 +++ 41 files changed, 1362 insertions(+), 449 deletions(-) create mode 100644 amoco/ui/graphics/__init__.py create mode 100644 amoco/ui/graphics/gtk_/__init__.py create mode 100644 amoco/ui/graphics/kivy_/__init__.py create mode 100644 amoco/ui/graphics/qt_/__init__.py create mode 100644 amoco/ui/graphics/term.py create mode 100644 amoco/ui/signals.py create mode 100644 amoco/ui/views.py create mode 100644 doc/conf.py create mode 100644 doc/index.rst create mode 100644 tests/samples/arm/helloworld.s create mode 100755 tests/samples/arm/hw create mode 100755 tests/samples/sparc/saverestore create mode 100644 tests/samples/sparc/saverestore.s mode change 100755 => 100644 tests/samples/x86/loop_simple.elf mode change 100755 => 100644 tests/samples/x86/prefixes.elf create mode 100644 tests/test_main_target.py create mode 100644 tests/test_ui_render.py diff --git a/README.rst b/README.rst index 7b77b71..3523069 100644 --- a/README.rst +++ b/README.rst @@ -1338,6 +1338,21 @@ Please see `LICENSE`_. Changelog ========= +- `v2.4.3`_ + + * add ui.graphics packages (emptied) + * add ui.views module with support for block/func/xfunc + * add ui.render.vltable class to pretty print tables + * improve instruction formatter class to access pp tokens + * cleaner itercfg and lbackward algorithms + * add vecw expression class to represent 'widened' vec expressions + * improve Memory write of vec expressions + * improve widening and fixpoint in func.makemap() + * add 'type' attribute (std/pc/flags/stack/other) + * define register type for x86 arch + * fix some x86/64 decoding/formating/semantics + * update travis config, fix pytest vs. Token. + - `v2.4.2`_ * merge support for pygments pretty printing methods (in ui.render module) @@ -1351,7 +1366,6 @@ Changelog * add sparc coprocessor registers * update README - - `v2.4.1`_ * add lbackward analysis and func.makemap() implementations @@ -1430,6 +1444,7 @@ Changelog .. _ply: http://www.dabeaz.com/ply/ .. _zodb: http://www.zodb.org .. _LICENSE: https://github.com/bdcht/amoco/blob/release/LICENSE +.. _v2.4.3: https://github.com/bdcht/amoco/releases/tag/v2.4.3 .. _v2.4.2: https://github.com/bdcht/amoco/releases/tag/v2.4.2 .. _v2.4.1: https://github.com/bdcht/amoco/releases/tag/v2.4.1 .. _v2.4.0: https://github.com/bdcht/amoco/releases/tag/v2.4.0 diff --git a/amoco/arch/arm/v7/formats.py b/amoco/arch/arm/v7/formats.py index 39ce1ba..e727542 100644 --- a/amoco/arch/arm/v7/formats.py +++ b/amoco/arch/arm/v7/formats.py @@ -4,22 +4,24 @@ from .utils import * from amoco.arch.core import Formatter +from amoco.ui.render import Token, TokenListJoin + def mnemo(i): m = i.mnemonic if hasattr(i,'setflags') and i.setflags: m += 'S' if hasattr(i,'cond') and i.cond!=CONDITION_AL: m += '.%s'%CONDITION[i.cond][0] - return '%s'%(m.lower()).ljust(12) + return [(Token.Mnemonic,'%s'%(m.lower()).ljust(12))] def regs(i,limit=None): ops = i.operands if limit: ops = ops[:limit] - return ['{0}'.format(r) for r in ops] + return [(Token.Register,'{0}'.format(r)) for r in ops] def reglist(i,pos=-1): l = i.operands[pos] - return "{%s}"%(', '.join(['{0}'.format(r) for r in l])) + return [(Token.Register,"{%s}"%(', '.join(['{0}'.format(r) for r in l])))] def deref(i,pos=-2): assert len(i.operands)>2 @@ -37,7 +39,7 @@ def deref(i,pos=-2): loc = '[%s], %s'%(base, ostr) else: loc = '[%s], %s'%(base,ostr) - return [loc] + return [(Token.Memory,loc)] def label(i,pos=0): _pc = i.address @@ -45,11 +47,11 @@ def label(i,pos=0): pcoffset = 4 if internals['isetstate']==0 else 2 _pc = _pc + 2*pcoffset offset = i.operands[pos] - return '*'+str(_pc+offset) + return [(Token.Address,'*'+str(_pc+offset))] def setend(i): endian_specifier = 'BE' if i.set_bigend else 'LE' - return mnemo(i)+endian_specifier + return mnemo(i)+[(Token.Literal,endian_specifier)] def plx(i): m = mnemo(i) @@ -59,23 +61,23 @@ def plx(i): ostr = '#%c%d'%(sign,offset.value) else: ostr = sign+str(offset) - loc = '[%s, %s]'%(base, ostr) + loc = [(Token.Memory,'[%s, %s]'%(base, ostr))] return m+loc def specreg(i): spec_reg = "%s_"%apsr if i.write_nzcvq: spec_reg += 'nzcvq' if i.write_g: spec_reg += 'g' - return '%s, %s'%(i.operands[0],spec_reg) + return [(Token.Register,'%s, %s'%(i.operands[0],spec_reg))] -format_allregs = [lambda i: ', '.join(regs(i))] +format_allregs = [lambda i: TokenListJoin(', ',regs(i))] format_default = [mnemo]+format_allregs format_sreg = format_default format_label = [mnemo, label] -format_adr = [mnemo, lambda i: '{0}, '.format(i.operands[0]), lambda i: label(i,1)] +format_adr = [mnemo, lambda i: regs(i,1), lambda i: label(i,1)] format_bits = format_default -format_reglist = [mnemo, (lambda i: ', '.join(regs(i,-1))), reglist] -format_deref = [mnemo, lambda i: ', '.join(regs(i,-2)+deref(i,-2))] +format_reglist = [mnemo, (lambda i: TokenListJoin(', ',regs(i,-1))), reglist] +format_deref = [mnemo, lambda i: TokenListJoin(', ',regs(i,-2)+deref(i,-2))] format_plx = [plx] format_msr = [mnemo, specreg] format_setend = [setend] diff --git a/amoco/arch/core.py b/amoco/arch/core.py index ef1d6a1..259a393 100644 --- a/amoco/arch/core.py +++ b/amoco/arch/core.py @@ -14,6 +14,8 @@ from amoco.logger import Log logger = Log(__name__) +from amoco.ui.render import Token,highlight + type_unpredictable = -1 type_undefined = 0 type_data_processing = 1 @@ -93,15 +95,19 @@ def __repr__(self): def set_formatter(cls,f): cls.formatter = f - #default formatter: - def formatter(self,i): - m = i.mnemonic - o = ','.join(map(str,i.operands)) - return '%s %s'%(m,o) + @staticmethod + def formatter(i,toks=False): + t = (Token.Mnemonic,i.mnemonic) + t+= [(Token.Literal,op) for op in map(str,i.operands[0:1])] + t+= [(Token.Literal,', '+op) for op in map(str,i.operands[1:])] + return t if toks else highlight(t) def __str__(self): return self.formatter(i=self) + def toks(self): + return self.formatter(i=self,toks=True) + def __getstate__(self): return (self.bytes, self.type, @@ -470,16 +476,17 @@ def getparts(self,i): fmts = self.formats.get(i.spec.hook.func_name,self.default) return fmts - def __call__(self,i): + def __call__(self,i,toks=False): s=[] for f in self.getparts(i): if hasattr(f,'format'): - # It is a string - s.append(f.format(i=i)) + t = f.format(i=i) else: - # It is a function - s.append(f(i)) - return ''.join(s) + t = f(i) + if isinstance(t,str): + t = [(Token.Literal,t)] + s.extend(t) + return s if toks else highlight(s) # ispec format parser: diff --git a/amoco/arch/sparc/asm.py b/amoco/arch/sparc/asm.py index e4f363c..b51c11c 100644 --- a/amoco/arch/sparc/asm.py +++ b/amoco/arch/sparc/asm.py @@ -551,18 +551,18 @@ def i_flush(ins,fmap):raise NotImplementedError @__pcnpc def i_FPop1(ins,fmap): - raise InstructionError + raise NotImplementedError @__pcnpc def i_FPop2(ins,fmap): - raise InstructionError + raise NotImplementedError @__pcnpc def i_CPop1(ins,fmap): - raise InstructionError + raise NotImplementedError @__pcnpc def i_CPop2(ins,fmap): - raise InstructionError + raise NotImplementedError @__pcnpc def i_unimp(ins,fmap): - raise InstructionError + raise NotImplementedError diff --git a/amoco/arch/sparc/formats.py b/amoco/arch/sparc/formats.py index fa265c9..51e5398 100644 --- a/amoco/arch/sparc/formats.py +++ b/amoco/arch/sparc/formats.py @@ -204,7 +204,7 @@ def label(i): SPARC_V8_full = Formatter(SPARC_V8_full_formats) -def SPARC_V8_synthetic(null,i): +def SPARC_V8_synthetic(null,i,toks=False): s = SPARC_V8_full(i) return SPARC_Synthetic_renaming(s, i) diff --git a/amoco/arch/x64/spec_ia32e.py b/amoco/arch/x64/spec_ia32e.py index fed8ff0..e20322d 100644 --- a/amoco/arch/x64/spec_ia32e.py +++ b/amoco/arch/x64/spec_ia32e.py @@ -136,6 +136,9 @@ def ia32_strings(obj): # imm8: @ispec_ia32("16>[ {6a} ib(8) ]", mnemonic = "PUSH", type=type_data_processing) @ispec_ia32("16>[ {cd} ib(8) ]", mnemonic = "INT", type=type_control_flow) +def ia32_imm8(obj,ib): + obj.operands = [env.cst(ib,8)] + @ispec_ia32("16>[ {eb} ib(8) ]", mnemonic = "JMP", type=type_control_flow) @ispec_ia32("16>[ {e2} ib(8) ]", mnemonic = "LOOP", type=type_control_flow) @ispec_ia32("16>[ {e1} ib(8) ]", mnemonic = "LOOPE", type=type_control_flow) @@ -285,7 +288,8 @@ def ia32_imm_rel(obj,cc,cb): def ia32_imm_rel(obj,cc,data): obj.cond = CONDITION_CODES[cc] size = obj.misc['opdsz'] or 32 - if size==16: raise InstructionError(obj) + if size==16 or data.size0: s.pop() + return s def oprel(i): to = i.misc['to'] - if to is not None: return '*'+str(to) + if to is not None: + return [(Token.Address,'*'+str(to))] if (i.address is not None) and i.operands[0]._is_cst: v = i.address + i.operands[0].signextend(32) + i.length i.misc['to'] = v - return '*'+str(v) - return '.%+d'%i.operands[0].value + return [(Token.Address,'*'+str(v))] + return [(Token.Constant,'.%+d'%i.operands[0].value)] # main intel formats: format_intel_default = (mnemo,opers) @@ -56,7 +58,7 @@ def oprel(i): format_intel_rel = (mnemo,oprel) -# formats: +# intel formats: IA32_Intel_formats = { 'ia32_strings' : format_intel_str, 'ia32_mov_adr' : format_intel_ptr, @@ -70,38 +72,84 @@ def oprel(i): IA32_Intel = Formatter(IA32_Intel_formats) IA32_Intel.default = format_intel_default -# highlighted formatter: -#----------------------- -from amoco.ui import render - -def IA32_Intel_tokenize(i): - toks = [] - for f in IA32_Intel.getparts(i): - if f is pfx: toks.append((render.Token.Prefix,f(i))) - elif f is mnemo: toks.append((render.Token.Mnemonic,f(i))) - elif f is oprel: - s = f(i) - if s.startswith('*'): - t = render.Token.Address +#------------------------------------------------------------------------------ +# AT&T formats: + +def mnemo_att(i): + mnemo = i.mnemonic.replace('cc','') + opdsz = i.misc['opdsz'] + if opdsz==16: mnemo+='w' + elif opdsz==8: mnemo+='b' + elif hasattr(i,'cond'): + mnemo += i.cond[0].split('/')[0] + return [(Token.Mnemonic,'{: <12}'.format(mnemo.lower()))] + +def deref_att(op): + assert op._is_mem + disp = '%+d'%op.a.disp if op.a.disp else '' + seg = '%s:'%op.a.seg if (op.a.seg is not '') else '' + b = op.a.base + if b._is_reg: + bis = '(%{})'.format(b) + else: + assert b._is_eqn + if b.op.symbol == '*': + bis = '(,%{},{})'.format(b.l,b.r) + else: + bis = '(%{},%{},{})'.format(b.l,b.r.l,b.r.r) + s = '%s%s%s'%(seg,disp,bis) + return [(Token.Memory,s)] + +def opers_att(i): + s = [] + for op in reversed(i.operands): + if op._is_mem: + s.extend(deref(op)) + elif op._is_cst: + if i.misc['imm_ref'] is not None: + s.append((Token.Address,str(i.misc['imm_ref']))) + elif op.sf: + s.append((Token.Constant,'$%+d'%op.value)) else: - t = render.Token.Constant - toks.append((t,s)) - elif f is opers: - for op in i.operands: - if op._is_reg: toks.append((render.Token.Register,str(op))) - elif op._is_mem: toks.append((render.Token.Memory,deref(op))) - elif op._is_cst: - if i.misc['imm_ref'] is not None: - toks.append((render.Token.Address,str(i.misc['imm_ref']))) - elif op.sf: - toks.append((render.Token.Constant,'%+d'%op.value)) - else: - toks.append((render.Token.Constant,str(op))) - toks.append((render.Token.Literal,', ')) - if toks[-1][0] is render.Token.Literal: toks.pop() + s.append((Token.Constant,str(op))) + elif op._is_reg: + s.append((Token.Register,'%{}'.format(op))) else: - toks.append((render.Token.Comment,f(i))) - return toks + raise ValueError,op + s.append((Token.Literal,', ')) + if len(s)>0: s.pop() + return s + +def oprel_att(i): + to = i.misc['to'] + if to is not None: + return [(Token.Address,'*'+str(to))] + if (i.address is not None) and i.operands[0]._is_cst: + v = i.address + i.operands[0].signextend(32) + i.length + i.misc['to'] = v + return [(Token.Address,'*'+str(v))] + return [(Token.Constant,'$.%+d'%i.operands[0].value)] + +# main at&t formats: +format_att_default = (mnemo_att,opers_att) + +format_att_ptr = (mnemo_att,opers_att) + +format_att_str = (pfx,mnemo_att,opers_att) + +format_att_rel = (mnemo_att,oprel_att) + +# formats: +IA32_ATT_formats = { + 'ia32_strings' : format_att_str, + 'ia32_mov_adr' : format_att_ptr, + 'ia32_ptr_ib' : format_att_ptr, + 'ia32_ptr_iwd' : format_att_ptr, + 'ia32_rm8' : format_att_ptr, + 'ia32_rm32' : format_att_ptr, + 'ia32_imm_rel' : format_att_rel, +} -def IA32_Intel_highlighted(null, i): - return render.highlight(IA32_Intel_tokenize(i)) +IA32_ATT = Formatter(IA32_ATT_formats) +IA32_ATT.default = format_att_default +# diff --git a/amoco/arch/x86/spec_ia32.py b/amoco/arch/x86/spec_ia32.py index 4042058..b169917 100644 --- a/amoco/arch/x86/spec_ia32.py +++ b/amoco/arch/x86/spec_ia32.py @@ -137,6 +137,9 @@ def ia32_strings(obj): @ispec_ia32("16>[ {d4} ib(8) ]", mnemonic = "AAM", type=type_data_processing) @ispec_ia32("16>[ {6a} ib(8) ]", mnemonic = "PUSH", type=type_data_processing) @ispec_ia32("16>[ {cd} ib(8) ]", mnemonic = "INT", type=type_control_flow) +def ia32_imm8(obj,ib): + obj.operands = [env.cst(ib,8)] + @ispec_ia32("16>[ {eb} ib(8) ]", mnemonic = "JMP", type=type_control_flow) @ispec_ia32("16>[ {e2} ib(8) ]", mnemonic = "LOOP", type=type_control_flow) @ispec_ia32("16>[ {e1} ib(8) ]", mnemonic = "LOOPE", type=type_control_flow) @@ -299,6 +302,7 @@ def ia32_imm_rel(obj,cc,cb): def ia32_imm_rel(obj,cc,data): obj.cond = CONDITION_CODES[cc] size = obj.misc['opdsz'] or 32 + if data.size1: s.pop() - s.append((Token.Literal,'%s}'%pad)) + s.append((render.Token.Literal,'%s}'%pad)) return s def eval(self,env): @@ -848,11 +873,18 @@ def restruct(self): self.smask[ra[0]:rb[1]] = [(ra[0],rb[1])]*(rb[1]-ra[0]) self.restruct() break + elif not (na._is_def or nb._is_def): + self.parts[(ra[0],rb[1])] = top(rb[1]-ra[0]) + self.parts.pop(ra) + self.parts.pop(rb) + self.smask[ra[0]:rb[1]] = [(ra[0],rb[1])]*(rb[1]-ra[0]) + self.restruct() + break ## def depth(self): return sum((p.depth() for p in self)) -## + ## #------------------------------------------------------------------------------ # mem holds memory fetches, ie a read operation of length size, in segment seg, @@ -877,7 +909,7 @@ def __str__(self): return 'M%d%s%s'%(self.size,n,self.a) def toks(self,**kargs): - return [(Token.Memory,str(self))] + return [(render.Token.Memory,str(self))] def eval(self,env): a = self.a.eval(env) @@ -920,13 +952,14 @@ def __str__(self): return '%s(%s%s)'%(self.seg,self.base,d) def toks(self,**kargs): - return [(Token.Address,str(self))] + return [(render.Token.Address,str(self))] def simplify(self): self.base,offset = extract_offset(self.base) self.disp += offset if isinstance(self.seg,exp): self.seg = self.seg.simplify() + if not self.base._is_def: self.disp=0 return self # default segment handler does not care about seg value: @@ -956,6 +989,8 @@ def slicer(x,pos,size): if rst==0: a = ptr(x.a.base,x.a.seg,x.a.disp+off) return mem(a,size) + elif x._is_cmp: + return x[pos:pos+size] return slc(x,pos,size) #------------------------------------------------------------------------------ @@ -989,6 +1024,10 @@ def setref(self,ref): self.__protect = True self.ref = ref + @property + def type(self): + if self._is_reg: return self.x.type + def raw(self): return "%s[%d:%d]"%(str(self.x),self.pos,self.pos+self.size) @@ -1001,12 +1040,14 @@ def __str__(self): return self.ref or self.raw() def toks(self,**kargs): - if self._is_reg: return [(Token.Register,str(self))] - subpart = [(Token.Literal,'[%d:%d]'%(self.pos,self.pos+self.size))] + if self._is_reg: return [(render.Token.Register,str(self))] + subpart = [(render.Token.Literal,'[%d:%d]'%(self.pos,self.pos+self.size))] return self.x.toks(**kargs)+subpart def __hash__(self): return hash(self.raw()) + def depth(self): return 2*self.x.depth() + def eval(self,env): n = self.x.eval(env) return n[self.pos:self.pos+self.size] @@ -1015,12 +1056,20 @@ def eval(self,env): # the sliced mem object. def simplify(self): self.x = self.x.simplify() + if not self.x._is_def: return top(self.size) if self.x._is_mem and self.size%8==0: off,rst = divmod(self.pos,8) if rst==0: a = ptr(self.x.a.base,self.x.a.seg,self.x.a.disp+off) return mem(a,self.size) - return self + if self.x._is_eqn and self.x.op.type==2: + r = self.x.r[self.pos:self.pos+self.size] + if self.x.op.unary: + return self.x.op(r) + l = self.x.l[self.pos:self.pos+self.size] + return self.x.op(l,r) + else: + return self # slice of a slice: @_checkarg_slice @@ -1077,9 +1126,9 @@ def __str__(self): def toks(self,**kargs): ttest = self.tst.toks(**kargs) - ttest.append((Token.Literal,' ? ')) + ttest.append((render.Token.Literal,' ? ')) ttrue = self.l.toks(**kargs) - ttrue.append((Token.Literal,' : ')) + ttrue.append((render.Token.Literal,' : ')) tfalse = self.r.toks(**kargs) return ttest+ttrue+tfalse @@ -1108,14 +1157,19 @@ def eval(self,env): else : return r def simplify(self): - if self.l is self.r: return self.l + if self.l == self.r: return self.l self.tst = self.tst.simplify() self.l = self.l.simplify() self.r = self.r.simplify() if self.tst==bit1: return self.l elif self.tst==bit0: return self.r + if not self.tst._is_def: + return vec([self.l,self.r]).simplify() return self + def depth(self): + return (self.tst.depth()+self.l.depth()+self.r.depth())/3. + #------------------------------------------------------------------------------ # oper returns a possibly simplified op() object (see below) @@ -1167,18 +1221,18 @@ def __str__(self): def toks(self,**kargs): l = self.l.toks(**kargs) - l.insert(0,(Token.Literal,'(')) + l.insert(0,(render.Token.Literal,'(')) r = self.r.toks(**kargs) - r.append((Token.Literal,')')) - return l+[(Token.Literal,self.op.symbol)]+r + r.append((render.Token.Literal,')')) + return l+[(render.Token.Literal,self.op.symbol)]+r def simplify(self): - minus = (self.op.symbol=='-') l = self.l.simplify() r = self.r.simplify() - if not l._is_def or not r._is_def: - return top(self.size) + minus = (self.op.symbol=='-') if self.prop<4: + if l._is_def==0: return l + if r._is_def==0: return r # arithm/logic normalisation: # push cst to the right if l._is_cst: @@ -1239,12 +1293,13 @@ def __str__(self): def toks(self,**kargs): r = self.r.toks(**kargs) - r.append((Token.Literal,')')) - return [(Token.Literal,'(%s'%self.op.symbol)]+r + r.append((render.Token.Literal,')')) + return [(render.Token.Literal,'(%s'%self.op.symbol)]+r def simplify(self): - self.r = self.r.simplify() - if not self.r._is_def: return top(self.size) + r = self.r.simplify() + if r._is_def==0: return r + self.r = r return eqn1_helpers(self) def depth(self): @@ -1326,7 +1381,14 @@ def __mul__(self,op): # basic simplifier: #------------------ -op.limit(30) +def configure(**kargs): + from amoco.config import get_module_conf + conf = get_module_conf('cas') + conf.update(kargs) + if conf['complexity']: + op.limit(conf['complexity']) + +configure() def symbols_of(e): if e is None: return [] @@ -1363,7 +1425,6 @@ def complexity(e): # helpers for unary expressions: def eqn1_helpers(e): assert e.op.unary - if not e.r._is_def: return e.r if e.r._is_cst: return e.op(e.r) if e.r._is_vec: @@ -1392,7 +1453,7 @@ def eqn1_helpers(e): def eqn2_helpers(e): if complexity(e.r)>e.threshold: e.r = top(e.r.size) if complexity(e.l)>e.threshold: e.l = top(e.l.size) - if not (e.l._is_def | e.r._is_def): return top(e.size) + if not (e.r._is_def and e.l._is_def): return top(e.size) if e.l._is_eqn and e.l.r._is_cst and e.l.op.unary==0: xop = e.op*e.l.op if xop: @@ -1465,17 +1526,16 @@ def extract_offset(e): # the merge function in the mapper module. # The simplify method uses the complexity measure to # eventually "reduce" the expression to top with a hard-limit -# currently set to >30. +# currently set to op.threshold. # ----------------------------------------------------- class vec(exp): - __slots__ = ['l','w'] + __slots__ = ['l'] _is_def = True _is_vec = True - def __init__(self,l=None,w=False): + def __init__(self,l=None): if l is None: l = [] self.l = l - self.w = w size = 0 for e in self.l: if e.size>size: size=e.size @@ -1486,39 +1546,45 @@ def __init__(self,l=None,w=False): def __str__(self): s = ','.join(map(str,self.l)) - w = ',...' if self.w else '' - return '[%s%s]'%(s,w) + return '[%s]'%(s) - def simplify(self): + def toks(self,**kargs): + t = [] + for x in self.l: + t.extend(x.toks(**kargs)) + t.append((render.Token.Literal,', ')) + if len(t)>0: t.pop() + t.insert(0,(render.Token.Literal,'[')) + t.append((render.Token.Literal,']')) + return t + + def simplify(self,widening=False): l = [] for e in self.l: ee = e.simplify() - if ee._is_vec: l.extend(ee.l) + if ee._is_vec: + l.extend(ee.l) + if isinstance(ee,vecw): + widening = True else: l.append(ee) self.l = [] for e in l: if e in self.l: continue self.l.append(e) - if len(self.l)==0: return exp(self.size) + if len(self.l)==1: + return self.l[0] + if widening: + return vecw(self) cl = map(complexity,self.l) - if max(cl)>30.: + if sum(cl,0.)>op.threshold: return top(self.size) - if self.w or (len(self.l)>1): - return self - else: - return self.l[0] + return self def eval(self,env): l = [] for e in self.l: l.append(e.eval(env)) - return vec(l,self.w) - - def addr(self,env): - l = [] - for e in self.l: - l.append(e.addr(env)) - return vec(l,self.w) + return vec(l) def depth(self): if self.size==0: return 0. @@ -1529,15 +1595,48 @@ def __getitem__(self,i): sta,sto,stp = i.indices(self.size) l = [] for e in self.l: - l.append(slicer(e,sta,sto-sta)) - return vec(l,self.w) + l.append(e[sta:sto]) + return vec(l) def __contains__(self,x): return (x in self.l) - # the only atom that is considered True is the cst(1,1) (ie bit1 below) def __nonzero__(self): return all([e.__nonzero__() for e in self.l]) -## +class vecw(top): + __slots__ = ['l'] + _is_def = 0 + _is_vec = True + def __init__(self,v): + self.l = v.l + self.size = v.size + self.sf = False + + def __str__(self): + s = ','.join(map(str,self.l)) + return '[%s, ...]'%(s) + + def toks(self,**kargs): + t = [] + for x in self.l: + t.extend(x.toks(**kargs)) + t.append((render.Token.Literal,', ')) + if len(t)>0: t.pop() + t.insert(0,(render.Token.Literal,'[')) + t.append((render.Token.Literal,', ...]')) + return t + + def eval(self,env): + v = vec([x.eval(env) for x in self.l]) + return vecw(v) + + @_checkarg_slice + def __getitem__(self,i): + sta,sto,stp = i.indices(self.size) + l = [] + for e in self.l: + l.append(e[sta:sto]) + return vecw(vec(l)) +## diff --git a/amoco/cas/mapper.py b/amoco/cas/mapper.py index a106fc2..a94b05c 100644 --- a/amoco/cas/mapper.py +++ b/amoco/cas/mapper.py @@ -11,6 +11,7 @@ from amoco.cas.tracker import generation from amoco.system.core import MemoryMap from amoco.arch.core import Bits +from amoco.ui.render import vltable # a mapper is a symbolic functional representation of the execution # of a set of instructions. @@ -21,7 +22,6 @@ # individual separated zones. # conds : is the list of conditions that must be True for the mapper # csi : is the optional interface to a "concrete" state -# to be valid. class mapper(object): assume_no_aliasing = False @@ -53,8 +53,17 @@ def __str__(self): return '\n'.join(["%s <- %s"%x for x in self]) def pp(self,**kargs): - lv = [(l.pp(**kargs),v.pp(**kargs)) for (l,v) in self] - return '\n'.join(["%s <- %s"%x for x in lv]) + t = vltable() + t.rowparams['sep'] = ' <- ' + for (l,v) in self: + if l._is_reg: v = v[0:v.size] + lv = (l.toks(**kargs)+ + [(render.Token.Column,'')]+ + v.toks(**kargs)) + t.addrow(lv) + if t.colsize[0]>18: t.colsize[0]=18 + if t.colsize[1]>58: t.colsize[1]=58 + return str(t) def __getstate__(self): return (self.__map,self.csi) @@ -153,13 +162,13 @@ def _Mem_read(self,a,l): try: res = self.__Mem.read(a,l) except MemoryError,e: # no zone for location a; - res = [top(l*8)] + res = [exp(l*8)] if exp._endian==-1: res.reverse() P = [] cur = 0 for p in res: plen = len(p) - if isinstance(p,exp) and not p._is_def: + if isinstance(p,exp) and (p._is_def is False): if self.csi: p = self.csi(mem(a,p.size,disp=cur)) else: @@ -171,7 +180,14 @@ def _Mem_read(self,a,l): return composer(P) def _Mem_write(self,a,v): - self.__Mem.write(a,v) + if a.base._is_vec: + locs = (ptr(l,a.seg,a.disp) for l in a.base.l) + else: + locs = (a,) + iswide = not a.base._is_def + for l in locs: + self.__Mem.write(l,v,deadzone=iswide) + if (l in self.__map): del self.__map[l] # just a convenient wrapper around M/R: def __getitem__(self,k): @@ -193,8 +209,12 @@ def __setitem__(self,k,v): return if k._is_slc and not loc._is_reg: raise ValueError('memory location slc is not supported') - elif k._is_ptr or k._is_mem: + elif loc._is_ptr: r = v + oldr = self.__map.get(loc,None) + if oldr is not None and oldr.size>r.size: + r = composer([r,oldr[r.size:oldr.size]]) + self._Mem_write(loc,r) self.__map.lastw = len(self.__map)+1 else: r = self.R(loc) @@ -203,11 +223,6 @@ def __setitem__(self,k,v): r[0:loc.size] = loc pos = k.pos if k._is_slc else 0 r[pos:pos+k.size] = v.simplify() - if loc._is_ptr: - oldr = self.__map.get(loc,None) - if oldr is not None and oldr.size>r.size: - r = composer([r,oldr[r.size:oldr.size]]) - self._Mem_write(loc,r) self.__map[loc] = r def update(self,instr): @@ -243,6 +258,7 @@ def eval(self,m): mm = mapper(csi=self.csi) for c in self.conds: cc = c.eval(m) + if not cc._is_def: continue if cc==1: continue if cc==0: logger.error("invalid mapper eval: cond %s is false"%c) @@ -260,6 +276,7 @@ def rcompose(self,m): mcopy = m.use() for c in self.conds: cc = c.eval(m) + if not cc._is_def: continue if cc==1: continue if cc==0: logger.error("invalid mapper eval: cond %s is false"%c) @@ -315,16 +332,21 @@ def assume(self,conds): from amoco.cas.smt import * # union of two mappers: -def merge(m1,m2): +def merge(m1,m2,widening=None): m1 = m1.assume(m1.conds) m2 = m2.assume(m2.conds) mm = mapper() for loc,v1 in m1: if loc._is_ptr: - v2 = m2[mem(loc,v1.size)] + seg = loc.seg + disp = loc.disp + if loc.base._is_vec: + v2 = vec([m2[mem(l,v1.size,seg,disp)] for l in loc.base.l]) + else: + v2 = m2[mem(loc,v1.size)] else: v2 = m2[loc] - vv = vec([v1,v2]).simplify() + vv = vec([v1,v2]).simplify(widening) mm[loc] = vv for loc,v2 in m2: if mm.has(loc): continue @@ -332,16 +354,6 @@ def merge(m1,m2): v1 = m1[mem(loc,v2.size)] else: v1 = m1[loc] - vv = vec([v1,v2]).simplify() + vv = vec([v1,v2]).simplify(widening) mm[loc] = vv return mm - -def widening(m): - w = False - for loc,v in m: - if loc._is_reg: v = v[0:loc.size] - if v._is_vec and len(v.l)>2: - w = True - v.w = w - return w - diff --git a/amoco/cas/smt.py b/amoco/cas/smt.py index 8e89a07..eddccd2 100644 --- a/amoco/cas/smt.py +++ b/amoco/cas/smt.py @@ -57,42 +57,42 @@ def ctr(self): has_solver = True -def newvar(pfx,e,solver): - s = '' if solver is None else '%d'%solver.ctr +def newvar(pfx,e,slv): + s = '' if slv is None else '%d'%slv.ctr return z3.BitVec("%s%s"%(pfx,s),e.size) -def top_to_z3(e,solver=None): - return newvar('_top',e,solver) +def top_to_z3(e,slv=None): + return newvar('_top',e,slv) -def cst_to_z3(e,solver=None): +def cst_to_z3(e,slv=None): return z3.BitVecVal(e.v,e.size) -def cfp_to_z3(e,solver=None): +def cfp_to_z3(e,slv=None): return z3.RealVal(e.v) -def reg_to_z3(e,solver=None): +def reg_to_z3(e,slv=None): return z3.BitVec(e.ref,e.size) -def comp_to_z3(e,solver=None): +def comp_to_z3(e,slv=None): e.simplify() - parts = [x.to_smtlib(solver) for x in e] + parts = [x.to_smtlib(slv) for x in e] parts.reverse() if len(parts)>1: return z3.Concat(*parts) else: return parts[0] -def slc_to_z3(e,solver=None): - x = e.x.to_smtlib(solver) +def slc_to_z3(e,slv=None): + x = e.x.to_smtlib(slv) return z3.Extract(int(e.pos+e.size-1),int(e.pos),x) -def ptr_to_z3(e,solver=None): - return e.base.to_smtlib(solver)+e.disp +def ptr_to_z3(e,slv=None): + return e.base.to_smtlib(slv)+e.disp -def mem_to_z3(e,solver=None): +def mem_to_z3(e,slv=None): e.simplify() M = z3.Array('M',z3.BitVecSort(e.a.size),z3.BitVecSort(8)) - p = e.a.to_smtlib(solver) + p = e.a.to_smtlib(slv) b = [] for i in range(0,e.length): b.insert(0,M[p+i]) @@ -100,45 +100,45 @@ def mem_to_z3(e,solver=None): if len(b) > 1: return z3.Concat(*b) return b[0] -def cast_z3_bool(x,solver=None): - b = x.to_smtlib(solver) +def cast_z3_bool(x,slv=None): + b = x.to_smtlib(slv) if not z3.is_bool(b): assert b.size()==1 b = (b==z3.BitVecVal(1,1)) return b -def cast_z3_bv(x,solver=None): - b = x.to_smtlib(solver) +def cast_z3_bv(x,slv=None): + b = x.to_smtlib(slv) if z3.is_bool(b): b = z3.If(b, z3.BitVecVal(1,1), z3.BitVecVal(0,1)) return b -def tst_to_z3(e,solver=None): +def tst_to_z3(e,slv=None): e.simplify() - z3t = cast_z3_bool(e.tst,solver) - l = cast_z3_bv(e.l,solver) - r = cast_z3_bv(e.r,solver) + z3t = cast_z3_bool(e.tst,slv) + l = cast_z3_bv(e.l,slv) + r = cast_z3_bv(e.r,slv) return z3.If(z3t, l, r) def tst_verify(e,env): t = e.tst.eval(env).simplify() - zt = cast_z3_bool(t) - s = z3.Solver() + s = solver() + zt = cast_z3_bool(t,s) for c in env.conds: - s.add(cast_z3_bool(c)) - s.push() - s.add(zt) - rtrue = (s.check()==z3.sat) - s.pop() - s.add(z3.Not(zt)) - rfalse = (s.check()==z3.sat) + s.solver.add(cast_z3_bool(c,s)) + s.solver.push() + s.solver.add(zt) + rtrue = (s.solver.check()==z3.sat) + s.solver.pop() + s.solver.add(z3.Not(zt)) + rfalse = (s.solver.check()==z3.sat) if rtrue and rfalse: return t if rtrue: return bit1 if rfalse: return bit0 # mapper conds are unsatisfiable: raise ValueError(e) -def op_to_z3(e,solver=None): +def op_to_z3(e,slv=None): e.simplify() l,r = e.l,e.r op = e.op @@ -146,8 +146,8 @@ def op_to_z3(e,solver=None): elif op.symbol == '//' : op = operator.rshift elif op.symbol == '>>>': op = z3.RotateRight elif op.symbol == '<<<': op = z3.RotateLeft - z3l = l.to_smtlib(solver) - z3r = r.to_smtlib(solver) + z3l = l.to_smtlib(slv) + z3r = r.to_smtlib(slv) if z3.is_bool(z3l): z3l = _bool2bv1(z3l) if z3.is_bool(z3r): @@ -161,16 +161,16 @@ def op_to_z3(e,solver=None): res = _bool2bv1(res) return res -def uop_to_z3(e,solver=None): +def uop_to_z3(e,slv=None): e.simplify() r = e.r op = e.op - z3r = r.to_smtlib(solver) + z3r = r.to_smtlib(slv) if z3.is_bool(z3r): z3r = _bool2bv1(z3r) return op(z3r) -def vec_to_z3(e,solver=None): +def vec_to_z3(e,slv=None): # flatten vec: e.simplify() # translate vec list to z3: @@ -179,7 +179,7 @@ def vec_to_z3(e,solver=None): zx = x.to_smtlib() beqs.append(zx) if len(beqs)==0: return exp(e.size) - if solver is None: + if slv is None: # if no solver is provided, it needs to be # a list of boolean equations if all([z3.is_bool(x) for x in beqs]): @@ -191,8 +191,8 @@ def vec_to_z3(e,solver=None): # if the solver is provided (default) # then a new local variable is added which # should equal one of the z3 expression. - var = newvar('_var',e,solver) - solver.solver.add(z3.Or([var==x for x in beqs])) + var = newvar('_var',e,slv) + slv.solver.add(z3.Or([var==x for x in beqs])) return var def _bool2bv1(z): @@ -210,11 +210,12 @@ def _bool2bv1(z): tst.to_smtlib = tst_to_z3 tst.verify = tst_verify op.to_smtlib = op_to_z3 - uop.to_smtlib = uop_to_z3 + uop.to_smtlib = uop_to_z3 vec.to_smtlib = vec_to_z3 + vecw.to_smtlib = top_to_z3 -def to_smtlib(e,solver=None): - return e.to_smtlib(solver) +def to_smtlib(e,slv=None): + return e.to_smtlib(slv) def model_to_mapper(r,locs): m = mapper() diff --git a/amoco/cfg.py b/amoco/cfg.py index bd1a94c..cf8014b 100644 --- a/amoco/cfg.py +++ b/amoco/cfg.py @@ -10,6 +10,7 @@ logger = Log(__name__) from grandalf.graphs import Vertex,Edge,Graph +from grandalf.layouts import SugiyamaLayout from amoco.system.core import MemoryZone @@ -23,6 +24,7 @@ class node(Vertex): def __init__(self,acode): Vertex.__init__(self,data=acode) self.name = self.data.name + self.view = self.data.view def __repr__(self): return '<%s [%s] at 0x%x>'%(self.__class__.__name__,self.name,id(self)) diff --git a/amoco/code.py b/amoco/code.py index 7deea8c..8064fb7 100644 --- a/amoco/code.py +++ b/amoco/code.py @@ -4,27 +4,42 @@ # Copyright (C) 2006-2011 Axel Tillequin (bdcht3@gmail.com) # published under GPLv2 license +""" +The code module defines classes that represent assembly blocks, functions, +and *external functions*. +""" + +import pdb +import heapq from collections import defaultdict + from amoco.cas.mapper import * from amoco.config import conf from amoco.logger import Log logger = Log(__name__) -#------------------------------------------------------------------------------ -# A block instance is a 'continuous' sequence of instructions. -#------------------------------------------------------------------------------ +from amoco.ui.views import blockView, funcView, xfuncView + +from amoco.ui.render import Token,vltable + +#------------------------------------------------------------------------------- class block(object): - __slots__=['_map','instr','_name','misc','_helper'] + """ + A block instance is a 'continuous' sequence of instructions. + """ + __slots__=['_map','instr','_name','misc','_helper','view'] - # the init of a block takes a list of instructions and creates a map of it: def __init__(self, instrlist, name=None): + """ + the init of a block takes a list of instructions and creates a `map` of it + """ self._map = None - # base/offset need to be defined before code (used in setcode) self.instr = instrlist self._name = name self.misc = defaultdict(lambda :0) self._helper = None + self.view = blockView(self) @property def map(self): @@ -51,7 +66,10 @@ def length(self): @property def support(self): - return (self.address,self.address+self.length) if len(self.instr)>0 else (None,None) + if len(self.instr)>0: + return (self.address,self.address+self.length) + else: + return (None,None) def getname(self): return str(self.address) if not self._name else self._name @@ -94,22 +112,28 @@ def cut(self,address): # TODO: update misc annotations too return len(I)-pos - def __str__(self): - L = [] + def __vltable(self): + T = vltable() n = len(self.instr) - if conf.getboolean('block','header'): - L.append('# --- block %s ---' % self.name) + for i in self.instr: + ins2 = i.toks() + if isinstance(ins2,str): ins2 = [(Token.Literal,ins2)] + ins = [ (Token.Address,'{:<10}'.format(i.address)), + (Token.Column,''), + (Token.Literal,"'%s'"%(i.bytes.encode('hex'))), + (Token.Column,'') ] + T.addrow(ins+ins2) if conf.getboolean('block','bytecode'): - bcs = [ "'%s'"%(i.bytes.encode('hex')) for i in self.instr ] pad = conf.getint('block','padding') or 0 - maxlen = max(map(len,bcs))+pad - bcs = [ s.ljust(maxlen) for s in bcs ] - else: - bcs = ['']*n - ins = [ ('{:<10}'.format(i.address),i.formatter(i)) for i in self.instr ] - for j in range(n): - L.append('%s %s %s'%(ins[j][0],bcs[j],ins[j][1])) - return '\n'.join(L) + T.colsize[1] += pad + if conf.getboolean('block','header'): + T.header = ('# --- block %s ---' % self.name).ljust(T.width,'-') + if conf.getboolean('block','footer'): + T.footer = '-'*T.width + return T + + def __str__(self): + return str(self.__vltable()) def __repr__(self): return '<%s object (name=%s) at 0x%08x>'%(self.__class__.__name__,self.name,id(self)) @@ -148,6 +172,7 @@ def __init__(self, g=None, name=None): self._name = name self.misc = defaultdict(lambda :0) self._helper = None + self.view = funcView(self) @property def address(self): @@ -163,97 +188,76 @@ def support(self): smax = max((b.address+b.length for b in self.blocks)) return (smin,smax) - def backward(self,node): - D = self.cfg.dijkstra(node,f_io=-1) - logger.verbose('computing backward map from %s',node.name) - return self.makemap(tagged=D.keys()) - #(re)compute the map of the entire function cfg: - def makemap(self,tagged=None,withmap=None): - _map = None - if tagged is None: tagged = self.cfg.sV - if self.cfg.order()==0: return - # get entrypoint: - t0 = self.cfg.roots() - if len(t0)==0: - logger.warning("function %s seems recursive: first block taken as entrypoint",self) - t0 = [self.cfg.sV[0]] - if len(t0)>1: - logger.warning("%s map computed with first entrypoint",self) - t0 = t0[0] - assert (t0 in tagged) - t0map = t0.data._map - if withmap is not None: - t0map <<= withmap - # init structs: - # spool is the list of current walking "heads" each having a mapper that captures - # execution up to this point, waitl is the list of nodes that have been reach - # by a path but are waiting for some other paths to reach them in order to collect - # and ultimately merge associated mappers before going into spool. - spool = [(t0,t0map)] - waitl = defaultdict(lambda :[]) - visit = defaultdict(lambda :0) - dirty = defaultdict(lambda :0) + def makemap(self,withmap=None,widening=True): + # spawn a cfg layout to detect loops and allow to + # walk the cfg by using the nodes rank. + gr = self.view.layout + gr.init_all() + # init the walking process heap queue and heads: + # spool is a heap queue ordered by node rank. It is associated + # with the list of current walking "heads" (a mapper that captures + # execution up to this block. count = 0 - # lets walk func cfg: + spool = [] + heads = {} + for t in gr.layers[0]: + heapq.heappush(spool,(0,t)) + tmap = t.data._map + if withmap is not None: + tmap <<= withmap + heads[t] = tmap + # lets walk the function's cfg, in rank priority order: while len(spool)>0: - n,m = spool.pop(0) - if dirty[n]: continue count += 1 logger.progress(count,pfx='in %s makemap: '%self.name) - E = n.e_out() - exit = True - # we update spool/waitl with target nodes - for e in E: - visit[e]=1 - if e.data and any([m(c)==0 for c in e.data]): continue + # take lowest ranked node from spool: + l,n = heapq.heappop(spool) + m = heads.pop(n) + # keep head for exit or merging points: + if len(n.e_out())==0 or len(n.e_in())>1: heads[n] = m + # we want to update the mapper by going through all edges out of n: + for e in n.e_out(): tn = e.v[1] - if not (tn in tagged): continue - exit = False - # if tn is a loop entry, n is marked as a loop end: - if tn.data.misc[tag.LOOP_START] and self.cfg.path(tn,n,f_io=1): - if not n.data.misc[tag.LOOP_END]: - logger.verbose('loop end at node %s'%n.name) - n.data.misc[tag.LOOP_END] += 1 - tm = tn.data.map.assume(e.data) + # compute constraints that lead to this path with this mapper: + econd = [] + if e.data: + econd = [m(c) for c in e.data] + # increment loop index for widening: + if e in gr.alt_e: + n.data.misc[tag.LOOP_END]+=1 + tn.data.misc[tag.LOOP_START]+=1 + # compute new mapper state: try: - waitl[tn].append(m>>tm) - except ValueError: + # apply edge contraints to the current mapper and + # try to execute target: + mtn = m.assume(econd)>>tn.data.map + except ValueError,err: logger.warning("link %s ignored"%e) - # if target node has been reach by all parent path, we - # can merge its mappers and proceed, otherwise it stays - # in wait list and we take next node from spool - if all(visit[x] for x in tn.e_in()): - wtn = waitl[tn] - if len(wtn)>0: - spool.append((tn,reduce(merge,wtn))) - del waitl[tn] - # if its an exit node, we update _map and check if widening is possible - if exit: - _map = merge(_map,m) if _map else m - if widening(_map): - # if widening has occured, we stop walking the loop by - # removing the associated spool node: - for s in spool: - if self.cfg.path(s[0],n,f_io=1): - dirty[s[0]] = 1 - logger.verbose('widening needed at node %s'%n.name) - # if spool is empty but wait list is not then we check if - # its a loop entry and update spool: - if len(spool)==0 and len(waitl)>0: - tn = waitl.keys()[0] - # if tn has output edges its a loop entry: - if len(tn.e_out())>0: - if not tn.data.misc[tag.LOOP_START]: - logger.verbose('loop start at node %s'%tn.name) - tn.data.misc[tag.LOOP_START] = 1 - spool.append((tn,reduce(merge,waitl[tn]))) - del waitl[tn] - assert len(waitl)==0 - if len(tagged)'%(pfx,self.cst,self.parent.name, cnd) + cnd = [str(x) for x in (self.econd or [])] + parent = self.parent + if parent is not None: parent = parent.name + return '<%s_target %s by %s %s>'%(pfx,self.cst,parent,cnd) # ----------------------------------------------------------------------------- @@ -205,10 +219,6 @@ def init_spool(self,loc): self.spool = [_target(loc,None)] def update_spool(self,vtx,parent): - if vtx is None: return - # if vtx was visited before targets have been added already: - if len(vtx.e_out())>0 or vtx in (s.parent for s in self.spool): - return T = self.get_targets(vtx,parent) if len(T)>0: if vtx.data.misc['tbc']: @@ -257,6 +267,9 @@ def add_call_node(self,vtx,parent,econd): vtx = self.G.add_vertex(vtx) return vtx + def check_func(self,vtx): + pass + def check_ext_target(self,t): if t.cst is None: return False if t.cst._is_ext: @@ -265,6 +278,7 @@ def check_ext_target(self,t): e = cfg.link(t.parent,vtx,data=t.econd) e = t.parent.c.add_edge(e) self.update_spool(e.v[1],t.parent) + self.check_func(e.v[1]) return True return False @@ -291,14 +305,13 @@ def itercfg(self,loc=None): # proceed with exploration of every spool element: while len(self.spool)>0: t = self.spool.pop(order) - if t.dirty: continue parent = t.parent econd = t.econd if self.check_ext_target(t): continue for b in self.iterblocks(loc=t.cst): vtx = G.get_by_name(b.name) or cfg.node(b) - b = vtx.data + do_update = (vtx not in G) # if block is a FUNC_START, we add it as a new graph component (no link to parent), # otherwise we add the new (parent,vtx) edge. if parent is None: @@ -306,15 +319,18 @@ def itercfg(self,loc=None): elif parent.data.misc[code.tag.FUNC_CALL]>0: vtx = self.add_call_node(vtx,parent,econd) else: - e = cfg.link(parent,vtx,data=econd) - e = G.add_edge(e) - if e is not None: + e_ = cfg.link(parent,vtx,data=econd) + e = G.add_edge(e_) + if e is e_: logger.verbose('edge %s added'%e) # now we try to populate spool with target addresses of current block: - self.update_spool(vtx,parent) + if do_update: + self.update_spool(vtx,parent) + self.check_func(vtx) yield vtx - if not lazy or b.misc[code.tag.FUNC_END]: break - logger.verbose("lsweep fallback at %s"%b.name) + if (not do_update or not lazy or + vtx.data.misc[code.tag.FUNC_END]): break + logger.verbose("lsweep fallback at %s"%vtx.data.name) parent = vtx econd = None @@ -397,84 +413,56 @@ def get_targets(self,node,parent): class lbackward(fforward): policy = {'depth-first': False, 'branch-lazy': False, 'frame-aliasing':False} - def update_spool(self,vtx,parent): - if vtx is None: return - root = vtx.c.sV[0] - if root.data.misc['func']: return - T = self.get_targets(vtx,parent) + def check_func(self,node): + for t in self.spool: + if t.parent in node.c: + return + # create func object: + f = code.func(node.c) + alf = code.mapper.assume_no_aliasing + code.mapper.assume_no_aliasing = not self.policy['frame-aliasing'] + m = f.makemap() + # get pc @ node: + pc = self.prog.cpu.PC() + mpc = m(pc) + T = _target(mpc,node).expand() + # if a target is defined here, it means that func cfg is not completed + # so we can return now : if len(T)>0: - #self.spool.extend(filter(lambda t:t not in self.spool,T)) - self.spool.extend(T) - return - err = '%s analysis stopped at node %s'%(self.__class__.__name__,vtx.name) - logger.info(err) - vtx.data.misc['tbc'] = 1 + logger.verbose('extending cfg of %s (new target found)'%f) + else: + logger.info('lbackward: function %s done'%f) + f.map = m + self.prog.codehelper(func=f) + mpc = f.map(pc) + roots = f.view.layout.layers[0] + if len(roots)>1: + logger.verbose('lbackward: multiple entries into function %s ?!'%f) + assert len(roots)>0 + nroot = roots[0] + nroot.data.misc['func'] = f + try: + fsym = nroot.data.misc['callers'][0].data.misc['to'].ref + except (IndexError,TypeError,AttributeError): + fsym = 'f' + f.name = "%s:%s"%(fsym,nroot.name) + for cn in nroot.data.misc['callers']: + cnpc = cn.data.map(mpc) + fn = cfg.node(f) + e = cn.c.add_edge(cfg.link(cn,fn)) + logger.verbose('edge %s added'%str(e)) + T.extend(_target(cnpc,e.v[1]).expand()) + code.mapper.assume_no_aliasing = alf + self.spool.extend(T) def get_targets(self,node,parent): pc = self.prog.cpu.PC() alf = code.mapper.assume_no_aliasing code.mapper.assume_no_aliasing = not self.policy['frame-aliasing'] - # try fforward first: - T = fforward.get_targets(self,node,parent) - if len(T)>0: - code.mapper.assume_no_aliasing = alf - return T - # create func object: - f = code.func(node.c) - m = f.backward(node) - if m is None: - logger.verbose('dead end at %s'%node.name) - else: - m = m.use((pc,f.address)) - # get pc @ node: - mpc = m(pc) - T = _target(mpc,node).expand() - # if a target is defined here, it means that func cfg is not completed - # so we can return now : - if len(T)>0: - code.mapper.assume_no_aliasing = alf - return T - # otherwise if func cfg is complete compute pc out of function callers: - xpc = [] - # check if a leaf is still going to be explored - for x in f.cfg.leaves(): - if x in (s.parent for s in self.spool): - code.mapper.assume_no_aliasing = alf - return xpc - # cleanup spool: - for t in self.spool: - if t.parent.c is f.cfg: - if t.cst in [n.data.address for n in t.parent.N(+1)]: - t.dirty=True - else: - # the target in spool will create a new branch/leaf - # so we're not done yet... - return xpc - # f is now fully explored so we can "return" to callers: - logger.info('lbackward: function %s done'%f) - # if needed compute the full map: - if f.misc['partial']: m = f.makemap() - f.map = m - self.prog.codehelper(func=f) - mpc = f.map(pc) - roots = filter(lambda n: n.data.misc[code.tag.FUNC_START],f.cfg.sV) - if len(roots)<=0: - code.mapper.assume_no_aliasing = alf - return xpc - if len(roots)>1: - logger.verbose('lbackward: multiple entries into function %s ?!'%f) - nroot = roots[0] - nroot.data.misc['func'] = f - try: - fsym = nroot.data.misc['callers'][0].data.misc['to'].ref - except (IndexError,TypeError,AttributeError): - fsym = 'f' - f.name = "%s:%s"%(fsym,nroot.name) - for cn in nroot.data.misc['callers']: - cnpc = cn.data.map.use((pc,cn.data.address))(mpc) - fn = cfg.node(f) - e = cn.c.add_edge(cfg.link(cn,fn)) - xpc.extend(_target(cnpc,e.v[1]).expand()) + # make pc value explicit in every block: + node.data.map = node.data.map.use((pc,node.data.address)) + # try fforward: + T = super(lbackward,self).get_targets(node,parent) code.mapper.assume_no_aliasing = alf - return xpc + return T diff --git a/amoco/system/core.py b/amoco/system/core.py index 4505b07..6022cd9 100644 --- a/amoco/system/core.py +++ b/amoco/system/core.py @@ -10,7 +10,7 @@ from bisect import bisect_left -from amoco.cas.expressions import top +from amoco.cas.expressions import exp,top #------------------------------------------------------------------------------ # datadiv provides the API for manipulating data values extracted from memory. @@ -154,10 +154,11 @@ def write(self,vaddr,data): #------------------------------------------------------------------------------ class MemoryZone(object): - __slot__ = ['rel','_map','__cache'] + __slot__ = ['rel','_map','__cache','__dead'] def __init__(self,rel=None,D=None): self.rel = rel + self.__dead = False self._map = [] self.__cache = [] # speedup locate method if D != None and isinstance(D,dict): @@ -187,15 +188,17 @@ def locate(self,vaddr): # read l bytes starting at vaddr. # return value is a list of datadiv values, unmapped areas - # are returned as 'top' expressions. + # are returned as 'void' expressions : top if zone is marked as 'dead' + # or bottom otherwise. def read(self,vaddr,l): + void = top if self.__dead else exp res = [] i = self.locate(vaddr) if i is None: - if len(self._map)==0: return [top(l*8)] + if len(self._map)==0: return [void(l*8)] v0 = self._map[0].vaddr - if (vaddr+l)<=v0: return [top(l*8)] - res.append(top((v0-vaddr)*8)) + if (vaddr+l)<=v0: return [void(l*8)] + res.append(void((v0-vaddr)*8)) l = (vaddr+l)-v0 vaddr = v0 i = 0 @@ -204,14 +207,14 @@ def read(self,vaddr,l): try: data,ll = self._map[i].read(vaddr,ll) except IndexError: - res.append(top(ll*8)) + res.append(void(ll*8)) ll=0 break if data is None: vi = self.__cache[i] if vaddr < vi: l = min(vaddr+ll,vi)-vaddr - data = top(l*8) + data = void(l*8) ll -= l i -=1 if data is not None: @@ -222,7 +225,11 @@ def read(self,vaddr,l): return res # write data at address vaddr in map - def write(self,vaddr,data,res=False): + def write(self,vaddr,data,res=False,dead=False): + if dead: + self._map = [] + self._cache = [] + self.__dead = dead self.addtomap(mo(vaddr,data)) if res is True: self.restruct() @@ -327,11 +334,11 @@ def read(self,address,l): else: raise MemoryError(address) - def write(self,address,expr): + def write(self,address,expr,deadzone=False): r,o = self.reference(address) if not r in self._zones: self.newzone(r) - self._zones[r].write(o,expr) + self._zones[r].write(o,expr,deadzone) def restruct(self): for z in self._zones.itervalues(): z.restruct() diff --git a/amoco/ui/graphics/__init__.py b/amoco/ui/graphics/__init__.py new file mode 100644 index 0000000..bf0bef4 --- /dev/null +++ b/amoco/ui/graphics/__init__.py @@ -0,0 +1,3 @@ +from .term import engine as termengine + +default = termengine diff --git a/amoco/ui/graphics/gtk_/__init__.py b/amoco/ui/graphics/gtk_/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/amoco/ui/graphics/kivy_/__init__.py b/amoco/ui/graphics/kivy_/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/amoco/ui/graphics/qt_/__init__.py b/amoco/ui/graphics/qt_/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/amoco/ui/graphics/term.py b/amoco/ui/graphics/term.py new file mode 100644 index 0000000..5498db0 --- /dev/null +++ b/amoco/ui/graphics/term.py @@ -0,0 +1,3 @@ +class engine(object): + + pass diff --git a/amoco/ui/render.py b/amoco/ui/render.py index edcfd05..4db2752 100644 --- a/amoco/ui/render.py +++ b/amoco/ui/render.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- from cStringIO import StringIO -from amoco.config import conf + from amoco.logger import Log logger = Log(__name__) +import re + try: from pygments.token import Token from pygments.style import Style @@ -26,6 +28,9 @@ def __init__(self,**options): def format(self,tokensource,outfile): for t,v in tokensource: outfile.write(v) + Formats = { + 'null':NullFormatter(), + } else: logger.info("pygments package imported") has_pygments = True @@ -42,21 +47,237 @@ class DefaultStyle(Style): Token.Comment: '#8f8', Token.Name: 'underline #fff', Token.Tainted: 'bold #f00', + Token.Column: '#bbb', + Token.Hide: '#222', } Formats = { + 'Null':NullFormatter(), 'Terminal':TerminalFormatter(style=DefaultStyle), 'Terminal256':Terminal256Formatter(style=DefaultStyle), } default_formatter = NullFormatter() -if has_pygments: - if conf.has_option("ui","formatter"): - f = conf.get("ui","formatter") - default_formatter = Formats.get(f,default_formatter) + +def configure(**kargs): + from amoco.config import get_module_conf + conf = get_module_conf('ui') + conf.update(kargs) + f = conf['formatter'] + global default_formatter + default_formatter = Formats.get(f,default_formatter) + +configure() def highlight(toks,formatter=None,outfile=None): formatter = formatter or default_formatter outfile = outfile or StringIO() formatter.format(toks,outfile) return outfile.getvalue() + +def TokenListJoin(j,lst): + if isinstance(j,str): + j = (Token.Literal,j) + res = lst[0:1] + for x in lst[1:]: + res.append(j) + res.append(x) + return res + +class vltable(object): + ''' + variable length table: + ''' + def __init__(self,rows=None,formatter=None,outfile=None): + if rows is None: rows = [] + self.rows = rows + self.rowparams = {'colsize':{}, + 'hidden_c': set(), + 'squash_c': True, + 'formatter':formatter, + 'outfile':outfile, + } + self.maxlength = float('inf') + self.hidden_r = set() + self.hidden_c = self.rowparams['hidden_c'] + self.squash_r = True + self.colsize = self.rowparams['colsize'] + self.update() + self.header = '' + self.footer = '' + + def update(self,*rr): + for c in range(self.ncols): + cz = self.colsize.get(c,0) if len(rr)>0 else 0 + self.colsize[c] = max(cz,self.getcolsize(c,rr,squash=False)) + + def getcolsize(self,c,rr=None,squash=True): + cz = 0 + if not rr: rr = range(self.nrows) + for i in rr: + if self.rowparams['squash_c'] and (i in self.hidden_r): + if squash: continue + cz = max(cz,self.rows[i].colsize(c)) + return cz + + @property + def width(self): + sep = self.rowparams.get('sep','') + cs = self.ncols*len(sep) + return sum(self.colsize.values(),cs) + + def setcolsize(self,c,value): + self.colsize[c] = value + + def addrow(self,toks): + self.rows.append(tokenrow(toks)) + self.update(-1) + return self + + def hiderow(self,n): + self.hidden_r.add(n) + def showrow(self,n): + self.hidden_r.remove(n) + + def hidecolumn(self,n): + self.hidden_c.add(n) + def showcolumn(self,n): + self.hidden_c.remove(n) + + def showall(self): + self.hidden_r = set() + self.rowparams['hidden_c'] = set() + self.hidden_c = self.rowparams['hidden_c'] + return self + + def grep(self,regex,col=None,invert=False): + L = set() + R = range(self.nrows) + for i in R: + if i in self.hidden_r: continue + C = self.rows[i].rawcols(col) + for c,s in enumerate(C): + if c in self.hidden_c: + continue + if re.search(regex,s): + L.add(i) + break + if not invert: L = set(R)-L + for n in L: self.hiderow(n) + return self + + @property + def nrows(self): + return len(self.rows) + + @property + def ncols(self): + if self.nrows>0: + return max((r.ncols for r in self.rows)) + else: + return 0 + + def __str__(self): + s = [] + formatter=self.rowparams['formatter'] + outfile=self.rowparams['outfile'] + for i in range(self.nrows): + if i in self.hidden_r: + if not self.squash_r: + s.append(highlight([(Token.Hide, + self.rows[i].show(raw=True,**self.rowparams))], + formatter, + outfile, + )) + else: + s.append(self.rows[i].show(**self.rowparams)) + if len(s)>self.maxlength: + s = s[:self.maxlength-1] + s.append(highlight([(Token.Literal,'...')],formatter,outfile)) + if self.header: s.insert(0,self.header) + if self.footer: s.append(self.footer) + return '\n'.join(s) + + +class tokenrow(object): + def __init__(self,toks=None): + if toks is None: toks = [] + self.toks = toks + self.maxwidth = float('inf') + self.align = '<' + self.fill = ' ' + self.separator = '' + self.cols = self.cut() + + def cut(self): + C = [] + c = [] + for t in self.toks: + c.append(t) + if t[0]==Token.Column: + C.append(c) + c = [] + C.append(c) + return C + + def colsize(self,c): + if c>=len(self.cols): return 0 + return sum((len(t[1]) for t in self.cols[c] if t[0]!=Token.Column)) + + @property + def ncols(self): + return len(self.cols) + + def rawcols(self,j=None): + r = [] + cols = self.cols + if j is not None: cols = self.cols[j:j+1] + for c in cols: + r.append(''.join([t[1] for t in c])) + return r + + def show(self,raw=False,**params): + formatter = params.get('formatter',None) + outfile = params.get('outfile',None) + align = params.get('align',self.align) + fill = params.get('fill',self.fill) + sep = params.get('sep',self.separator) + width = params.get('maxwidth',self.maxwidth) + colsz = params.get('colsize') + hidden_c = params.get('hidden_c',set()) + squash_c = params.get('squash_c',True) + if raw: + formatter=None + outfile=None + r = [] + tz = 0 + for i,c in enumerate(self.cols): + toks = [] + sz = 0 + mz = colsz[i] + tz += mz + if tz>width: mz = mz-(tz-width) + skip = False + for tt,tv in c: + if tt==Token.Column: break + if skip: continue + toks.append([tt,tv]) + sz += len(tv) + if sz>mz: + q = (sz-mz) + toks[-1][1] = tv[0:-q]+'###' + skip = True + if sz': toks[0][1] = pad+toks[0][1] + if tt==Token.Column: + if sep: tv=sep + toks.append((tt,tv)) + if i in hidden_c: + if not squash_c: + toks = [(TokenHide,highlight(toks,None,None))] + else: + toks = [] + r.append(highlight(toks,formatter,outfile)) + return ''.join(r) diff --git a/amoco/ui/signals.py b/amoco/ui/signals.py new file mode 100644 index 0000000..07d2381 --- /dev/null +++ b/amoco/ui/signals.py @@ -0,0 +1,11 @@ +from amoco.config import conf +from amoco.logger import Log +logger = Log(__name__) + +try: + import blinker + has_blinker = True + +except ImportError: + logger.info('blinker package not found, no ui.signals defined') + has_blinker = False diff --git a/amoco/ui/views.py b/amoco/ui/views.py new file mode 100644 index 0000000..f83e71a --- /dev/null +++ b/amoco/ui/views.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +from amoco.config import conf +from amoco.logger import Log +logger = Log(__name__) + +from grandalf.layouts import SugiyamaLayout + +from amoco.ui import graphics + +class View(object): + backend = graphics.default + + def __init__(self,w=1,h=1,of=None): + self.w = w + self.h = h + self.xy = None + self.of = of + +class blockView(View): + def __init__(self,block): + super(blockView,self).__init__(of=block) + +class funcView(View): + def __init__(self,func): + super(funcView,self).__init__(of=func) + self.layout = SugiyamaLayout(func.cfg) + +class xfuncView(View): + def __init__(self,xfunc): + super(xfuncView,self).__init__(of=xfunc) diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..245ffdc --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,269 @@ +# -*- coding: utf-8 -*- +# +# amoco documentation build configuration file, created by +# sphinx-quickstart on Thu Sep 24 12:49:42 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx.ext.intersphinx', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'amoco' +copyright = u'2015, bdcht' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '2.4' +# The full version, including alpha/beta/rc tags. +release = '2.4.3' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'amocodoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'amoco.tex', u'amoco Documentation', + u'bdcht', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'amoco', u'amoco Documentation', + [u'bdcht'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'amoco', u'amoco Documentation', + u'bdcht', 'amoco', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None} + diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..5bf84fd --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,22 @@ +.. amoco documentation master file, created by + sphinx-quickstart on Thu Sep 24 12:49:42 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to amoco's documentation! +================================= + +Contents: + +.. toctree:: + :maxdepth: 2 + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/tests/conftest.py b/tests/conftest.py index e504fcc..df7e3f0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -77,3 +77,14 @@ def sc1(): @pytest.fixture(scope='session') def samples(): return samples_all + +@pytest.fixture(scope='session') +def x86samples(samples): + return filter(lambda s: 'x86/' in s, samples) + +@pytest.fixture(scope='session') +def ploop(x86samples): + for s in x86samples: + if 'loop_simple' in s: + return s + return None diff --git a/tests/samples/arm/helloworld.s b/tests/samples/arm/helloworld.s new file mode 100644 index 0000000..3a3a53b --- /dev/null +++ b/tests/samples/arm/helloworld.s @@ -0,0 +1,11 @@ +.file "helloworld.s" +.section ".text" +.align 4 +.global main +.type main, #function + +main: + push {r0,r10,pc} + ldr r3, [pc, #228] + cmp r3, #0 + diff --git a/tests/samples/arm/hw b/tests/samples/arm/hw new file mode 100755 index 0000000000000000000000000000000000000000..d8ac71fcd45055cb94cd18f954a46ff084ad64a6 GIT binary patch literal 33470 zcmeIu!D0~&eO+}JjQBPyp3i3?*NzVXz=&=Bh4blzF)4iziRbRdeikAX)_g; z{SIH}E$m#kt~b)1t~xpA=LakO-H%pSb7aY5&BEV(#$xrp-A~ey_0mf3P4%hVEUIDm zb*Ubl>a6MapL0HH7IXg){~zP!sj#RJOW|^{fU&p+PUA3IF&D8_i b&u0hLSkB979_=oxl-b!=uWjTlFRG}&7=C1U literal 0 HcmV?d00001 diff --git a/tests/samples/sparc/saverestore b/tests/samples/sparc/saverestore new file mode 100755 index 0000000000000000000000000000000000000000..61dfcf6a84de228d2ae1f3e25d121079699c621b GIT binary patch literal 66569 zcmeIw!D|&q90%~(L_>>0qx8_?CDw};c~r@v^wI}04J{!`)I$$t*~A3{$%}n&DGG%q zm;MPo6be1~2lUWmp@m+0?71hwi#HKGcoCi1-M1T``Uey~^WMIh@BC(GCLi*{>iQSi zTo$@E7v|zP8#WWmAq(Hv!{x9P$LGUBm{0Zi329AwVjH&4@ieayAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!CviPvDn_e?I$ZF?{gjV)*d>!tli5gAjfTf1Npe6vFQxh101&{QJQ> z+1viKwUw36md>wj+`O=~JXjuF4q-64w^tQgv9HEWe|tKr%DYt*!@>;L^cPviJoFNmAh?{}N;miwcvVqE4sqn&EI*xhaS>vzuc*Vi;#9$j<0 ZEbd$id3S+)yq^wicblGGzgIS;@DlMXq*(v} literal 0 HcmV?d00001 diff --git a/tests/samples/sparc/saverestore.s b/tests/samples/sparc/saverestore.s new file mode 100644 index 0000000..5bdfde6 --- /dev/null +++ b/tests/samples/sparc/saverestore.s @@ -0,0 +1,18 @@ + .file "saverestore.c" + .section ".text" + .align 4 + .proc 020 +main: + save %sp, -8, %sp + mov %i0, %o3 + mov %i1, %g3 + add %i1,%o3,%l2 + save + add %g3,%i3,%i0 + restore + sub %o0,%l2,%i2 + restore + jmp %o7+8 + nop + .size main, .-main + .ident "GCC: (GNU) 4.4.2" diff --git a/tests/samples/x86/loop_simple.elf b/tests/samples/x86/loop_simple.elf old mode 100755 new mode 100644 diff --git a/tests/samples/x86/prefixes.elf b/tests/samples/x86/prefixes.elf old mode 100755 new mode 100644 diff --git a/tests/test_arch_x86.py b/tests/test_arch_x86.py index 0bc1c65..9103a25 100644 --- a/tests/test_arch_x86.py +++ b/tests/test_arch_x86.py @@ -2,7 +2,10 @@ from amoco.arch.x86.cpu_x86 import * -instruction.set_formatter(IA32_Intel) +# enforce Intel syntax and NullFormatter output: +configure(format='Intel') +from amoco.ui import render +render.configure(formatter='Null') def test_decoder_000(): c = '\x90' diff --git a/tests/test_cas_exp.py b/tests/test_cas_exp.py index 3f971ce..3aa3824 100644 --- a/tests/test_cas_exp.py +++ b/tests/test_cas_exp.py @@ -86,6 +86,15 @@ def test_op(a,b): assert e.r.v == 0xffffffffL assert e.r.sf == True +def test_op_slc(a,b): + e = a^b + assert e[8:16] == a[8:16]^b[8:16] + e = composer([a[0:8],b[0:8]]) + x = (e&a[0:16])[0:8] + assert x._is_slc + x = x.simplify() + assert x._is_reg and x==a[0:8] + def test_ptr(a): p = ptr(a) q = ptr(a,disp=17) @@ -115,3 +124,22 @@ def test_vec(a): assert z.l[3]==0x13 assert (~v).l[3]==0xffffffef +def test_vecw(): + x = [cst(n) for n in range(5)] + v1 = vec(x) + v2 = vec(x[0:3]) + v3 = vec([v1,v2]).simplify() + assert len(v3.l)==5 + assert v3==v1 + v4 = vec([v1,v2]).simplify(widening=True) + assert not v4._is_def + assert len(v4.l)==5 + assert v4+1 == v4 + assert v4.depth()==float('inf') + assert v3[8:16].l == v4[8:16].l + +def test_top(r): + t = top(8) + assert t+3 == t + assert t^r[0:8] == t + assert (t==3) == top(1) diff --git a/tests/test_cas_smt.py b/tests/test_cas_smt.py index 0e70464..0b3c435 100644 --- a/tests/test_cas_smt.py +++ b/tests/test_cas_smt.py @@ -2,11 +2,14 @@ from amoco.cas.smt import * +op.limit(100) + @pytest.mark.skipif(not has_solver,reason="no smt solver loaded") def test_reg_bv(x,y): xl = slc(x,0,8,ref='xl') xh = slc(x,8,8,ref='xh') z = (x^cst(0xcafebabe,32))+(y+(x>>2)) + assert complexity(z)<100 m = solver([z==cst(0x0,32),xl==0xa,xh==0x84]).get_model() assert m is not None xv,yv = (m[v].as_long() for v in m) diff --git a/tests/test_main_target.py b/tests/test_main_target.py new file mode 100644 index 0000000..b07457c --- /dev/null +++ b/tests/test_main_target.py @@ -0,0 +1,41 @@ +import pytest + +import amoco + +def test_001(): + c = amoco.cas.expressions.cst(0x804849d,32) + t = amoco.main._target(c,None) + assert t.dirty is False + T = t.expand() + assert len(T)==1 + tc = T[0] + assert tc.cst._is_cst + assert tc.cst==c + +def test_002(ploop): + p = amoco.system.loader.load_program(ploop) + z = amoco.fforward(p) + c = amoco.cas.expressions.cst(0x804849d,32) + z.init_spool(c) + t = z.spool.pop(0) + assert isinstance(t,amoco.main._target) + assert z.check_ext_target(t) is False + assert t.cst._is_cst + assert t.econd is None + b0 = next(z.iterblocks(loc=t.cst)) + n0 = amoco.cfg.node(b0) + assert t.parent is None + z.add_root_node(n0) + assert z.G.C[0].sV[0] is n0 + z.update_spool(n0,t.parent) + assert len(z.spool)==1 + t = z.spool.pop(0) + assert t.cst == 0x80484d0 + b1 = next(z.iterblocks(t.cst)) + n1 = amoco.cfg.node(b1) + e0 = amoco.cfg.link(n0,n1,data=t.econd) + e = z.G.add_edge(e0) + assert e is e0 + z.update_spool(n1,n0) + assert len(z.spool)==2 + diff --git a/tests/test_system_core.py b/tests/test_system_core.py index 9f7cb39..cb7dc33 100644 --- a/tests/test_system_core.py +++ b/tests/test_system_core.py @@ -20,7 +20,8 @@ def test_memory_001(M,sc1): o = z._map[0] assert o.data.val==sc1 -def test_memory_002(M,p): +def test_memory_002(M,sc1,p): + M.write(0x0, sc1) M.write(p, 'A'*8) assert len(M._zones)==2 # write little-endian 16 bits constant: @@ -30,7 +31,10 @@ def test_memory_002(M,p): assert len(z._map)==3 assert M.read(p+3,1)[0]==0x42 -def test_memory_003(M,y): +def test_memory_003(M,sc1,p,y): + M.write(0x0, sc1) + M.write(p, 'A'*8) + M.write(p+2,cst(0x4243,16)) # overwrite string with symbolic reg y: M.write(cst(0x10,32), y) assert len(M._zones)==2 @@ -38,7 +42,11 @@ def test_memory_003(M,y): assert len(z._map)==3 assert z._map[1].data.val==y -def test_memory_004(M,p,y): +def test_memory_004(M,sc1,p,y): + M.write(0x0, sc1) + M.write(p, 'A'*8) + M.write(p+2,cst(0x4243,16)) + M.write(cst(0x10,32), y) # test big endian cases: z = M._zones[p.base] c = z._map[1].data.val diff --git a/tests/test_ui_render.py b/tests/test_ui_render.py new file mode 100644 index 0000000..dfe0b23 --- /dev/null +++ b/tests/test_ui_render.py @@ -0,0 +1,27 @@ +import pytest + +from amoco.ui.render import * + +@pytest.mark.skipif(not has_pygments,reason="no pygments") +def test_vltable(): + T = vltable() + T.addrow([(Token.Literal,'abcd'),(Token.Register,'eax'),(Token.Column,'<-'),(Token.Constant,'0x23')]) + T.addrow([(Token.Literal,'abcd'),(Token.Register,'ebx'),(Token.Column,'<-'),(Token.Mnemonic,'mov')]) + T.addrow([(Token.Literal,'abcdxxxxxxx'),(Token.Register,'eflags'),(Token.Column,'<-'),(Token.Memory,'M32(eax+1)')]) + assert T.nrows == 3 + assert T.ncols == 2 + assert T.colsize[0] == 17 + assert T.getcolsize(0) == 17 + T.hiderow(2) + assert T.getcolsize(0) == 7 + T.showrow(2) + T.grep('eax',invert=True) + assert T.hidden_r.issuperset((0,2)) + c0 = T.getcolsize(0) + assert c0 == 7 + T.setcolsize(0,c0) + T.squash_r = True + T.squash_c = True + s = str(T) + +