diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..18d9adf --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,20 @@ +--- +engines: + duplication: + enabled: true + config: + languages: + - python + fixme: + enabled: true + radon: + enabled: true + config: + python_version: 2 + pep8: + enabled: true +ratings: + paths: + - "**.py" +exclude_paths: +- tests/**/* diff --git a/.gitignore b/.gitignore index 287aa8b..f792bfe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ +*~ *.pyc parser.out parsetab.py *.i64 *.idb .DS_Store +.coverage +coverage.xml diff --git a/.travis.yml b/.travis.yml index 3e18897..f4523be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,23 @@ +sudo: false + language: python + python: - - "2.7" -install: "pip install -r requirements.txt" -script: python tests/run.py + - 2.7 + +install: + - pip install -r requirements.txt + +addons: + code_climate: + repo_token: + secure: ehr0JgLCt81b9ST4sj2eeStCHpUu119ZUi6jVELxgRwwMok8KHxSDjAE3JMX50AFYOU7zzfZECH8ZKICeDZE1vKjLI0CN7f6wv/4MNVoO50cpAlC4sjVNpxkWjf5DJwqQgkQ+R/Pe3pSoDRt4CY2DKFj0Mow4HwzjPIQGqiYNirr0UQh+aK4RSidYwHynssClMOjIgYIHwLikmMSEf/f8odJmlBQrebR2qDG0mTAGRVOgUziSizaoNSYrnLSFL1v5QJSihfa6AOPv6Zh+oRcq0r8MpEw6XVgSRB21zqRhVlXnGVSOn5Nzynbi4MalBjhT/TzJ0cDiTGLMuNvEtaGEjTbGWksXciI/Fd4LWKni5W/GNyOFaq5/d+3JYGSM7ncQKLZf9BOeAKiUuyl5A+/69AmDylSYMa+DPaNyllV2GOoiUoin8weyG4eHUthAgF4/bSzXjv8VCViRWaAqROXqhf6raJkEjt3G2eHPlv8KjPFrcQk8Oa9yu/GZJHh01w1ZBT9RgDutD2PunR7W4TlD7LZ8msEY6smtQfgagrV9rC35Mn9jwmyIFVA68dxJIPdkPIIDKmYlAJgj6hlLg2Kb5pOrd/S2f8jiqeUSahwBpetxy0OhqNgqO7pS8GXhxcxtmYOzZ6rWF0W+9M7QwV7RnZPEmVX1WbnOn2Wcx2R3kk= + +before_install: + - pip install codecov + +script: + - coverage run tests/run.py + +after_success: + - codecov diff --git a/README.md b/README.md index f6f7feb..e57a033 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ -decompiler ![Build Status](https://api.travis-ci.org/EiNSTeiN-/decompiler.svg) +[![Build Status](https://travis-ci.org/Maroc-OS/decompiler.svg)](https://travis-ci.org/Maroc-OS/decompiler) +[![Codacy Badge](https://api.codacy.com/project/badge/grade/6066dcab6432437f9a360b0663deeae8)](https://www.codacy.com/app/merruk-company/decompiler) +[![Code Climate](https://codeclimate.com/github/Maroc-OS/decompiler/badges/gpa.svg)](https://codeclimate.com/github/Maroc-OS/decompiler) +[![codecov.io](https://codecov.io/github/Maroc-OS/decompiler/coverage.svg?branch=master)](https://codecov.io/github/Maroc-OS/decompiler?branch=master) +[![Issue Count](https://codeclimate.com/github/Maroc-OS/decompiler/badges/issue_count.svg)](https://codeclimate.com/github/Maroc-OS/decompiler) ============== A multi-backends decompiler written in python. It currently supports IDA and Capstone. diff --git a/src/decompiler.py b/src/decompiler.py index 42d6dee..0d469ff 100644 --- a/src/decompiler.py +++ b/src/decompiler.py @@ -290,7 +290,7 @@ class decompiler_t(object): step_locals_renamed, step_ssa_removed, step_combined, - step_decompiled, + step_decompiled ] def __init__(self, disasm, ea): diff --git a/src/host/capstone/dis/intel.py b/src/host/capstone/dis/intel.py index a8a53ff..f144cc9 100644 --- a/src/host/capstone/dis/intel.py +++ b/src/host/capstone/dis/intel.py @@ -106,7 +106,6 @@ def get_operand_expression(self, ea, n): expr = value_t(op.imm, op.size*8) else: raise RuntimeError('%x: unhandled operand type: %s' % (ea, repr(op.type))) - return return expr diff --git a/src/host/dis.py b/src/host/dis.py index d9e6e54..173a1f8 100644 --- a/src/host/dis.py +++ b/src/host/dis.py @@ -23,7 +23,6 @@ except ImportError as e: print repr(e) traceback.print_exc() - pass except BaseException as e: print repr(e) traceback.print_exc() diff --git a/src/host/ida/dis/__init__.py b/src/host/ida/dis/__init__.py index 2fe2d18..6e26476 100644 --- a/src/host/ida/dis/__init__.py +++ b/src/host/ida/dis/__init__.py @@ -18,7 +18,7 @@ def disassembler_for_arch(arch_name=None): print 'Architecture: 64-bit intel.' return (ir.IR_INTEL_x64, ir.intel.ir_intel_x64, intel.disassembler) - raise RuntimeError("Don't know which arch to choose for %s" % (repr(filetype), )) + raise RuntimeError("Don't know which arch to choose for %s" % (repr(arch_name), )) def create(arch_name=None): """ Find the correct disassembler module for this host. diff --git a/src/host/ida/dis/intel.py b/src/host/ida/dis/intel.py index 7678b94..b600388 100644 --- a/src/host/ida/dis/intel.py +++ b/src/host/ida/dis/intel.py @@ -143,7 +143,6 @@ def get_operand_expression(self, ea, n): expr = value_t(addr, self.get_operand_size(op)) else: raise RuntimeError('%x: unhandled operand type: %s %s' % (ea, repr(op.type), repr(idc.GetOpnd(ea, 1)))) - return return expr diff --git a/src/host/ida/ui/decompiler_form.py b/src/host/ida/ui/decompiler_form.py index 6bbea42..a42e187 100644 --- a/src/host/ida/ui/decompiler_form.py +++ b/src/host/ida/ui/decompiler_form.py @@ -31,7 +31,7 @@ 'Locations renamed', 'Expressions propagated', 'Dead code pruned', - 'Decompiled', + 'Decompiled' ] class DecompilerForm(idaapi.PluginForm): @@ -69,7 +69,9 @@ def populate_form(self): for phase in decompilation_phase: self.phase_selection.addItem(phase) - self.phase_selection.setCurrentIndex(decompiler.STEP_DECOMPILED) + # case sensitive for step_decompiled and setCurrentIndex argument should be integer, + # so why not just (0)??? + self.phase_selection.setCurrentIndex(0) self.phase_selection.currentIndexChanged.connect(self.phase_selected) self.parent.setLayout(layout) @@ -80,13 +82,16 @@ def phase_selected(self, index): self.decompile(index) return - def decompile(self, wanted_step=decompiler.STEP_DECOMPILED): + def decompile(self, wanted_step=None): + + if not wanted_step: + wanted_step = decompiler.step_decompiled dis = host.dis.available_disassemblers['ida'].create() d = decompiler.decompiler_t(dis, self.ea) for step in d.steps(): - print 'Decompiler step: %u - %s' % (step, decompilation_phase[step]) + #~ print 'Decompiler step: %u - %s' % (step, decompilation_phase[step]) # this print is not treated correctelly. if step >= wanted_step: break diff --git a/src/host/ida/ui/main.py b/src/host/ida/ui/main.py index 392a9bf..9e6bfe5 100644 --- a/src/host/ida/ui/main.py +++ b/src/host/ida/ui/main.py @@ -31,15 +31,15 @@ def main(): try: hotkey_ctx if idaapi.del_hotkey(hotkey_ctx): - print("Hotkey unregistered!") + print "Hotkey unregistered!" del hotkey_ctx else: - print("Failed to delete hotkey!") + print "Failed to delete hotkey!" except: pass hotkey_ctx = idaapi.add_hotkey("F5", show_decompiler) if hotkey_ctx is None: - print("Failed to register hotkey!") + print "Failed to register hotkey!" del hotkey_ctx else: - print("Press F5 to decompile a function.") + print "Press F5 to decompile a function." diff --git a/src/ir/generic.py b/src/ir/generic.py index 4472bb1..65703f7 100644 --- a/src/ir/generic.py +++ b/src/ir/generic.py @@ -13,15 +13,15 @@ class ir_base(object): def is_return(self, ea): """ return True if this is a return instruction. """ - raise NotImplemented('base class must override this method') + raise NotImplementedError('base class must override this method') def has_jump(self, ea): """ return true if this instruction is a jump """ - raise NotImplemented('base class must override this method') + raise NotImplementedError('base class must override this method') def next_instruction_ea(self, ea): """ return the address of the next instruction. """ - raise NotImplemented('base class must override this method') + raise NotImplementedError('base class must override this method') def jump_branches(self, ea): """ if this instruction is a jump, yield the destination(s) @@ -30,12 +30,12 @@ def jump_branches(self, ea): note that the destination expression is usually a value_t representing an address within the function, however it may be any other operand type such as a register. """ - raise NotImplemented('base class must override this method') + raise NotImplementedError('base class must override this method') def generate_statements(self, ea): """ this is where the magic happens, this method yeilds one or more new statement corresponding to the given location. """ - raise NotImplemented('base class must override this method') + raise NotImplementedError('base class must override this method') ## following functions are typically implemented at the host level. they are used mostly to diff --git a/src/output/c.py b/src/output/c.py index e42a442..6fe2333 100644 --- a/src/output/c.py +++ b/src/output/c.py @@ -120,7 +120,7 @@ def __init__(self, function, indent=' '): self.function = function self.arch = function.arch self.indent = indent - self.display_labels = self.display_labels() + self.show_labels = self.display_labels() self.done_labels = None return @@ -315,53 +315,25 @@ def expression_tokens(self, obj): yield tok return - if type(obj) == sign_t: - yield token_keyword('SIGN') - l, r = self.matching('(', ')') - yield l - for tok in self.expression_tokens(obj.op): - yield tok - yield r - return - - if type(obj) == overflow_t: - yield token_keyword('OVERFLOW') - l, r = self.matching('(', ')') - yield l - for tok in self.expression_tokens(obj.op): - yield tok - yield r - return - - if type(obj) == parity_t: - yield token_keyword('PARITY') - l, r = self.matching('(', ')') - yield l - for tok in self.expression_tokens(obj.op): - yield tok - yield r - return - - if type(obj) == adjust_t: - yield token_keyword('ADJUST') - l, r = self.matching('(', ')') - yield l - for tok in self.expression_tokens(obj.op): - yield tok - yield r + if obj is None: + yield token_keyword('None') return - if type(obj) == carry_t: - yield token_keyword('CARRY') - l, r = self.matching('(', ')') - yield l - for tok in self.expression_tokens(obj.op): - yield tok - yield r + if type(obj) == sign_t: + obj_type = 'SIGN' + elif type(obj) == overflow_t: + obj_type = 'OVERFLOW' + elif type(obj) == parity_t: + obj_type = 'PARITY' + elif type(obj) == adjust_t: + obj_type = 'ADJUST' + elif type(obj) == carry_t: + obj_type = 'CARRY' + elif type(obj) == phi_t: + obj_type = 'Φ' return - if type(obj) == phi_t: - yield token_keyword('Φ') + if type(obj) == phi_t or obj_type == 'Φ': l, r = self.matching('(', ')') yield l for op in obj.operands: @@ -371,9 +343,13 @@ def expression_tokens(self, obj): yield token_character(' ') yield r return - - if obj is None: - yield token_keyword('None') + else: + yield token_keyword(obj_type) + l, r = self.matching('(', ')') + yield l + for tok in self.expression_tokens(obj.op): + yield tok + yield r return raise ValueError('cannot display object of type %s' % (obj.__class__.__name__, )) @@ -381,7 +357,7 @@ def expression_tokens(self, obj): def statement_tokens(self, obj, indent=0): if isinstance(obj, statement_t) and obj.ea is not None: - if obj.ea in self.display_labels and obj.ea not in self.done_labels: + if obj.ea in self.show_labels and obj.ea not in self.done_labels: yield token_global('loc_%x' % (obj.ea, )) yield token_character(':') yield token_character('\n') diff --git a/src/propagator.py b/src/propagator.py index 5f923a3..156cfa5 100644 --- a/src/propagator.py +++ b/src/propagator.py @@ -11,7 +11,6 @@ def __init__(self, function): def is_assignment(self, stmt): return isinstance(stmt.expr, assign_t) and \ isinstance(stmt.expr.op1, assignable_t) - return False def replace(self, defn, value, use): new = value.copy(with_definition=True) diff --git a/src/ssa.py b/src/ssa.py index 687cddf..5abf4c1 100644 --- a/src/ssa.py +++ b/src/ssa.py @@ -705,7 +705,7 @@ def rename_groups(self, phi, groups): else: print 'more than one group' print ' ', repr(groups) - raise 'not implemented' + raise NotImplementedError('not implemented.') return diff --git a/tests/unit/test_helper.py b/tests/unit/test_helper.py index cbf6886..43dc5cc 100644 --- a/tests/unit/test_helper.py +++ b/tests/unit/test_helper.py @@ -19,8 +19,7 @@ try: import capstone except ImportError as e: - print 'warning: Capstone tests are unavailable' - pass + print 'Capstone tests are unavailable\n\warning: %s' % e class callconv(object): def __init__(self, callconv):