Changelog: bv.write and bv.insert require a bytes object in python3 Architecture.assemble outputs a bytes object in python3, a str in python2 Architecture.assemble will throw a value error if it cannot assemble the given instruction API install script should be run in the version of python you want it installed in Fundamental python changes to be aware of: Unicode-type strings are now just str, consequently anything that came out as a unicode string before (annotations) are now just str. Longs no longer exist. They're just ints.
| -rw-r--r-- | suite/api_test.py | 43 | ||||
| m--------- | suite/binaries | 0 | ||||
| -rwxr-xr-x | suite/generator.py | 143 | ||||
| -rw-r--r-- | suite/oracle.pkl | bin | 1969636 -> 440689 bytes | |||
| -rw-r--r-- | suite/testcommon.py | 748 | ||||
| -rw-r--r-- | suite/unit.py | 206 |
diff --git a/suite/api_test.py b/suite/api_test.py index a162d26a..740f88e3 100644 --- a/suite/api_test.py +++ b/suite/api_test.py @@ -1,17 +1,10 @@ import unittest -import time import platform import os -import tempfile -import zipfile -import urllib -import subprocess -import getpass from binaryninja.setting import Setting from binaryninja.metadata import Metadata from binaryninja.demangle import demangle_gnu3, get_qualified_name from binaryninja.architecture import Architecture -from binaryninja.binaryview import BinaryViewType class SettingsAPI(unittest.TestCase): @@ -226,39 +219,3 @@ class DemanglerTest(unittest.TestCase): for i, test in enumerate(tests): t, n = demangle_gnu3(Architecture['x86'], test) assert self.get_type_string(t, n) == results[i] - - -class TimingTest(unittest.TestCase): - def unpackage_file(self, file): - if not os.path.exists(file): - with zipfile.ZipFile(file + ".zip", "r") as zf: - zf.extractall() - assert os.path.exists(file) - - def test_analysis_time(self): - if platform.system() != "Linux" or getpass.getuser() != "jenkins": - return - - start_time = time.time() - file_names = [os.path.join(os.path.dirname(__file__), "binaries", "quick3dcoreplugin.dll"), - os.path.join(os.path.dirname(__file__), "binaries", "md5"), - os.path.join(os.path.dirname(__file__), "binaries", "ls")] - - for file_name in file_names: - temp_name = next(tempfile._get_candidate_names()) + ".bndb" - self.unpackage_file(file_name) - try: - bv = BinaryViewType.get_view_of_file(file_name) - bv.create_database(temp_name) - bv.file.close() - bv = BinaryViewType.get_view_of_file(temp_name) - bv.file.close() - finally: - if os.path.exists(file_name): - os.unlink(file_name) - if os.path.exists(temp_name): - os.unlink(temp_name) - - time_s = time.time() - start_time - commit = subprocess.check_output(["git", "rev-parse", "HEAD"])[:-1] - conn = urllib.urlopen("https://script.google.com/macros/s/AKfycbxrZtlgLaWt3l95_7RyH9ceDdFIBm0VaR6jG1-4UDt6CFFCFzQ/exec?BuildID=%s&Test1=%.2f" % (commit, time_s)) diff --git a/suite/binaries b/suite/binaries -Subproject 7d5d207de812ccd7c4648331b00fa936ef0a82c +Subproject 96d08bbb4fdcf1b88c98165fcfe8474659c38d6 diff --git a/suite/generator.py b/suite/generator.py index 347d0d84..09ee00dc 100755 --- a/suite/generator.py +++ b/suite/generator.py @@ -10,41 +10,99 @@ import time unit_test_template = """#!/usr/bin/env python # This is an auto generated unit test file do not edit directly import os +import sys import unittest import pickle import zipfile +import difflib +from collections import Counter + +api_suite_path = os.path.join(os.path.dirname(__file__), {4}) +sys.path.append(api_suite_path) import testcommon -import binaryninja import api_test -import difflib +global verbose +verbose = False class TestBinaryNinjaAPI(unittest.TestCase): + # Returns a tuple of: + # bool : Two lists are equal + # string : The string diff + # Args: + # list + # list : (compare list one vs list two) + # string : anything additional wanted to be printed before the string diff + # bool : the ordering of the items in the two lists must be the same + def report(self, oracle, test, firstText='', strictOrdering = False): + stringDiff = "" + + equality = False + if not strictOrdering: + equality = (Counter(oracle) == Counter(test)) + else: + equality = (oracle == test) + + if equality: + return (True, '') + elif not strictOrdering: + try: + for elem in oracle: + test.remove(elem) + oracle.remove(elem) # If it's not in the test, it won't get here! + except ValueError: + pass + + differ = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) + skipped_lines = 0 + for delta in differ.compare(oracle, test): + if delta[0] == ' ': + skipped_lines += 1 + continue + if skipped_lines > 0: + stringDiff += "<---" + str(skipped_lines) + ' same lines--->\\n' + skipped_lines = 0 + delta = delta.replace(\'\\n\', '') + stringDiff += delta + \'\\n\' + + stringDiffList = stringDiff.split(\'\\n\') + + if len(stringDiffList) > 10: + if not verbose: + stringDiff = \'\\n\'.join(line if len(line) <= 100 else line[:100] + "...and " + str(len(line) - 100) + " more characters" for line in stringDiffList[:10]) + stringDiff += \'\\n\\n### And ' + str(len(stringDiffList)) + " more lines, use '-v' to show ###" + elif not verbose: + stringDiff = \'\\n\'.join(line if len(line) <= 100 else line[:100] + "...and " + str(len(line) - 100) + " more characters" for line in stringDiffList) + stringDiff = \'\\n\\n\' + firstText + stringDiff + return (equality, stringDiff) + @classmethod def setUpClass(self): self.builder = testcommon.TestBuilder("{3}") + pickle_path = os.path.join(os.path.dirname(__file__), "oracle.pkl") try: - #Python 2 does not have the encodings option - self.oracle_test_data = pickle.load(open(os.path.join("{0}", "oracle.pkl"), "rUb"), errors="ignore") + # Python 2 does not have the encodings option + self.oracle_test_data = pickle.load(open(pickle_path, "rb"), encoding='charmap') except TypeError: - self.oracle_test_data = pickle.load(open(os.path.join("{0}", "oracle.pkl"), "rU")) + self.oracle_test_data = pickle.load(open(pickle_path, "rb")) self.verifybuilder = testcommon.VerifyBuilder("{3}") def run_binary_test(self, testfile): testname = None - with zipfile.ZipFile(testfile, "r") as zf: + with zipfile.ZipFile(os.path.join(api_suite_path, testfile), "r") as zf: testname = zf.namelist()[0] - zf.extractall() + zf.extractall(path=api_suite_path) - self.assertTrue(os.path.exists(testname + ".pkl"), "Test pickle doesn't exist") + pickle_path = os.path.join(os.path.dirname(__file__), testname + ".pkl") + self.assertTrue(pickle_path, "Test pickle doesn't exist") try: - #Python 2 does not have the encodings option - binary_oracle = pickle.load(open(testname + ".pkl", "rUb"), errors="ignore") + # Python 2 does not have the encodings option + binary_oracle = pickle.load(open(pickle_path, "rb"), encoding='charmap') except TypeError: - binary_oracle = pickle.load(open(testname + ".pkl", "rU")) + binary_oracle = pickle.load(open(pickle_path, "rb")) - test_builder = testcommon.BinaryViewTestBuilder(testname, "{3}") + test_builder = testcommon.BinaryViewTestBuilder(testname) for method in test_builder.methods(): test = getattr(test_builder, method)() oracle = binary_oracle[method] @@ -53,22 +111,15 @@ class TestBinaryNinjaAPI(unittest.TestCase): result = getattr(test_builder, method).__doc__ result += ":\\n" - d = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in d.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\\n' - skipped_lines = 0 - delta = delta.replace('\\n', '') - result += delta + '\\n' - self.assertTrue(False, result) - os.unlink(testname) + report = self.report(oracle, test, result) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle + os.unlink(os.path.join(api_suite_path, testname)) {1}{2} if __name__ == "__main__": + if len(sys.argv) > 1: + if sys.argv[1] == '-v' or sys.argv[1] == '-V' or sys.argv[1] == '--verbose': + verbose = True test_suite = unittest.defaultTestLoader.loadTestsFromModule(api_test) test_suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestBinaryNinjaAPI)) @@ -81,11 +132,13 @@ binary_test_string = """ def test_binary__{0}(self): self.run_binary_test('{1}') """ + test_string = """ def {0}(self): oracle = self.oracle_test_data['{0}'] test = self.builder.{0}() - self.assertTrue(oracle == test, "%s:\\n'%s' does not equal\\n'%s'" % (self.builder.{0}.__doc__, oracle, test)) + report = self.report(oracle, test) + self.assertTrue(report[0], report[1]) # Test does not agree with oracle """ verify_string = """ @@ -97,7 +150,7 @@ verify_string = """ class OracleTestFile: def __init__(self, filename): self.f = open(filename + ".pkl", "wb") - self.pkl = pickle.Pickler(self.f) + self.pkl = pickle.Pickler(self.f, protocol=2) self.filename = filename self.oracle_test_data = {} @@ -120,7 +173,11 @@ class UnitTestFile: self.binary_tests = "" def close(self): - self.f.write(self.template.format(self.outdir, self.tests, self.binary_tests, self.test_store).encode('utf-8')) + api_path = os.path.relpath(os.path.dirname(__file__), start=self.outdir) + api_path = os.path.normpath(api_path) + api_path = map(lambda x: '"{0}"'.format(x), api_path.split(os.sep)) + api_path = '{0}'.format(', '.join(api_path)) + self.f.write(self.template.format(self.outdir, self.tests, self.binary_tests, self.test_store, api_path).encode('charmap')) self.f.close() def add_verify(self, test_name): @@ -135,8 +192,6 @@ class UnitTestFile: quiet = False - - def myprint(stuff): if not quiet: print(stuff) @@ -164,7 +219,7 @@ class TestStoreError(Exception): def generate(test_store, outdir, exclude_binaries): - if not os.path.isdir(test_store): + if not os.path.isdir(os.path.join(os.path.dirname(__file__), test_store)): raise TestStoreError("Specified test store is not a directory") unittest = UnitTestFile(os.path.join(outdir, "unit.py"), outdir, test_store) @@ -193,10 +248,12 @@ def generate(test_store, outdir, exclude_binaries): oraclefile = None if testfile.endswith(".pkl"): continue + elif testfile.endswith(".DS_Store"): + continue elif testfile.endswith(".zip"): # We have a zipped binary unzip it so we can rebaseline with zipfile.ZipFile(testfile, "r") as zf: - zf.extractall() + zf.extractall(path = os.path.dirname(__file__)) if not os.path.exists(testfile[:-4]): print("Error extracting testfile %s from zip: %s" % (testfile[:-4], testfile)) continue @@ -208,14 +265,16 @@ def generate(test_store, outdir, exclude_binaries): # We have a binary that isn't zipped use it as a new test case oraclefile = testfile + oraclefile_rel = os.path.relpath(oraclefile, start=os.path.dirname(__file__)) + # Now generate the oracle data - update_progress(progress, len(allfiles), oraclefile) - unittest.add_binary_test(test_store, oraclefile) + update_progress(progress, len(allfiles), oraclefile_rel) + unittest.add_binary_test(test_store, oraclefile_rel) binary_start_time = time.time() if exclude_binaries: continue - test_data = testcommon.BinaryViewTestBuilder(oraclefile, test_store) - binary_oracle = OracleTestFile(oraclefile) + test_data = testcommon.BinaryViewTestBuilder(oraclefile_rel) + binary_oracle = OracleTestFile(os.path.join(outdir, oraclefile_rel)) for method in test_data.methods(): binary_oracle.add_entry(test_data, method) binary_oracle.close() @@ -223,7 +282,7 @@ def generate(test_store, outdir, exclude_binaries): if not os.path.exists(oraclefile + ".zip"): with zipfile.ZipFile(oraclefile + ".zip", "w") as zf: - zf.write(oraclefile) + zf.write(oraclefile, os.path.relpath(oraclefile, start=os.path.dirname(__file__))) os.unlink(oraclefile) @@ -243,17 +302,13 @@ def main(): default=False, help="Exclude regeneration of binaries") parser.add_option("-o", "--outputdir", default="suite", dest="outputdir", action="store", type="string", - help="output directory where the unit.py and oracle.py files will be stored") - parser.add_option("-i", "--inputdir", default=os.path.join("suite", "binaries", "test_corpus"), + help="output directory where the unit.py and oracle.py files will be stored (relative to cwd)") + parser.add_option("-i", "--inputdir", default=os.path.join("binaries", "test_corpus"), dest="test_store", action="store", type="string", - help="input directory containing the binaries you which to generate unit tests from") + help="input directory containing the binaries you which to generate unit tests from (relative to this file)") options, args = parser.parse_args() - if not os.path.exists(os.path.join(os.getcwd(), 'suite')): - print("Error: Please run this script from the binaryninja-api root directory") - sys.exit(1) - myprint("[+] INFO: Using test store: %s" % options.test_store) if len(testcommon.get_file_list(options.test_store)) == 0: myprint("ERROR: No files in the test store %s" % testcommon.get_file_list(options.test_store)) diff --git a/suite/oracle.pkl b/suite/oracle.pkl Binary files differindex bf1a4b3b..f0be17d4 100644 --- a/suite/oracle.pkl +++ b/suite/oracle.pkl diff --git a/suite/testcommon.py b/suite/testcommon.py index 4e004630..285c24b5 100644 --- a/suite/testcommon.py +++ b/suite/testcommon.py @@ -1,29 +1,74 @@ import tempfile import pickle import os +import sys import zipfile import inspect import binaryninja as binja from binaryninja.binaryview import BinaryViewType, BinaryView from binaryninja.filemetadata import FileMetadata import subprocess -import traceback -import types +import re -def get_file_list(test_store): +# Dear people from the future: If you're adding tests or debuging an +# issue where python2 and python3 are producing different output +# for the same function and it's a issue of `longs`, run the output +# through this function. If it's a unicode/bytes issue, fix it in +# api/python/ +def fixOutput(outputList): + # Apply regular expression to detect python2 longs + splitList = [] + for elem in outputList: + if isinstance(elem, str): + splitList.append(re.split(r"((?<=[\[ ])0x[\da-f]+L|[\d]+L)", elem)) + else: + splitList.append(elem) + + # Resolve application of regular expression + result = [] + for elem in splitList: + if isinstance(elem, list): + newElem = [] + for item in elem: + if len(item) > 1 and item[-1] == 'L': + newElem.append(item[:-1]) + else: + newElem.append(item) + result.append(''.join(newElem)) + else: + result.append(elem) + return result + + +# Alright so this one is here for Binja functions that output <in set([blah, blah, blah])> +def fixSet(string): + # Apply regular expression + splitList = (re.split(r"((?<=<in set\(\[).*(?=\]\)>))", string)) + if len(splitList) > 1: + return splitList[0] + ', '.join(sorted(splitList[1].split(', '))) + splitList[2] + else: + return string + + +def get_file_list(test_store_rel): + test_store = os.path.join(os.path.dirname(__file__), test_store_rel) all_files = [] for root, dir, files in os.walk(test_store): for file in files: all_files.append(os.path.join(root, file)) - return all_files +def remove_low_confidence(type_string): + low_confidence_types = ["int32_t", "void"] + for lct in low_confidence_types: + type_string = type_string.replace(lct + " ", '') # done to resolve confidence ties + return type_string class Builder(object): def __init__(self, test_store): self.test_store = test_store - self.examples_dir = os.path.join(self.test_store, "..", "..", "..", "python", "examples") + # binja.log.log_to_stdout(binja.LogLevel.DebugLog) # Uncomment for more info def methods(self): methodnames = [] @@ -32,11 +77,13 @@ class Builder(object): methodnames.append(methodname) return methodnames - def unpackage_file(self, file): - if not os.path.exists(file): - with zipfile.ZipFile(file + ".zip", "r") as zf: - zf.extractall() - assert os.path.exists(file) + def unpackage_file(self, filename): + path = os.path.join(os.path.dirname(__file__), self.test_store, filename) + if not os.path.exists(path): + with zipfile.ZipFile(path + ".zip", "r") as zf: + zf.extractall(path = os.path.dirname(__file__)) + assert os.path.exists(path) + return os.path.relpath(path) class BinaryViewTestBuilder(Builder): @@ -48,61 +95,72 @@ class BinaryViewTestBuilder(Builder): - Function doc string used as 'on error' message - Should return: list of strings """ - def __init__(self, filename, test_store): - self.filename = filename - self.bv = BinaryViewType.get_view_of_file(filename) + def __init__(self, filename): + self.filename = os.path.join(os.path.dirname(__file__), filename) + self.bv = BinaryViewType.get_view_of_file(self.filename) if self.bv is None: print("%s is not an executable format" % filename) return def test_available_types(self): """Available types don't match""" - return [x.name for x in BinaryView(FileMetadata()).open(self.filename).available_view_types] + return ["Available Type: " + x.name for x in BinaryView(FileMetadata()).open(self.filename).available_view_types] def test_function_starts(self): """Function starts list doesnt match""" - return [hex(x.start) for x in self.bv.functions] + result = [] + for x in self.bv.functions: + result.append("Function start: " + hex(x.start)) + return fixOutput(result) def test_function_symbol_names(self): """Function.symbol.name list doesnt match""" - return [x.symbol.name + ' ' + str(x.symbol.type) + ' ' + hex(x.symbol.address) for x in self.bv.functions] + result = [] + for x in self.bv.functions: + result.append("Symbol: " + x.symbol.name + ' ' + str(x.symbol.type) + ' ' + hex(x.symbol.address)) + return fixOutput(result) def test_function_can_return(self): """Function.can_return list doesnt match""" - return [x.symbol.name + ' ' + str(x.symbol.type) + ' ' + hex(x.symbol.address) + ' ' + str(bool(x.can_return)) for x in self.bv.functions] + result = [] + for x in self.bv.functions: + result.append("function name: " + x.symbol.name + ' type: ' + str(x.symbol.type) + ' address: ' + hex(x.symbol.address) + ' can_return: ' + str(bool(x.can_return))) + return fixOutput(result) def test_function_basic_blocks(self): """Function basic_block list doesnt match (start, end, has_undetermined_outgoing_edges)""" bblist = [] for func in self.bv.functions: for bb in func.basic_blocks: - bblist.append(hex(bb.start) + ' ' + hex(bb.end) + ' ' + str(bb.has_undetermined_outgoing_edges)) - return bblist + bblist.append("basic block {} start: ".format(str(bb)) + hex(bb.start) + ' end: ' + hex(bb.end) + ' undetermined outgoing edges: ' + str(bb.has_undetermined_outgoing_edges)) + for anno in func.get_block_annotations(bb.start): + bblist.append("basic block {} function annotation: ".format(str(bb)) + str(anno)) + bblist.append("basic block {} test get self: ".format(str(bb)) + str(func.get_basic_block_at(bb.start))) + return fixOutput(bblist) def test_function_low_il_basic_blocks(self): - """"Function low_il_basic_block list doesnt match""" + """Function low_il_basic_block list doesnt match""" ilbblist = [] for func in self.bv.functions: for bb in func.low_level_il.basic_blocks: - ilbblist.append(hex(bb.start) + ' ' + hex(bb.end) + ' ' + str(len(bb.outgoing_edges))) - return ilbblist - + ilbblist.append("LLIL basic block {} start: ".format(str(bb)) + hex(bb.start) + ' end: ' + hex(bb.end) + ' outgoing edges: ' + str(len(bb.outgoing_edges))) + return fixOutput(ilbblist) + def test_function_med_il_basic_blocks(self): - """"Function med_il_basic_block list doesn't match""" + """Function med_il_basic_block list doesn't match""" ilbblist = [] for func in self.bv.functions: for bb in func.medium_level_il.basic_blocks: - ilbblist.append(hex(bb.start) + ' ' + hex(bb.end) + ' ' + str(len(bb.outgoing_edges))) - return ilbblist + ilbblist.append("MLIL basic block {} start: ".format(str(bb)) + hex(bb.start) + ' end: ' + hex(bb.end) + ' outgoing_edges: ' + str(len(bb.outgoing_edges))) + return fixOutput(ilbblist) def test_symbols(self): - """"Symbols list doesn't match""" - return [str(i) for i in sorted(self.bv.symbols)] + """Symbols list doesn't match""" + return ["Symbol: " + str(i) for i in sorted(self.bv.symbols)] def test_strings(self): """Strings list doesn't match""" - return [x.value + ' ' + str(x.type) + ' ' + hex(x.start) for x in self.bv.strings] - + return fixOutput(["String: " + str(x.value) + ' type: ' + str(x.type) + ' at: ' + hex(x.start) for x in self.bv.strings]) def test_low_il_instructions(self): """LLIL instructions produced different output""" @@ -110,42 +168,38 @@ class BinaryViewTestBuilder(Builder): for func in self.bv.functions: for bb in func.low_level_il.basic_blocks: for ins in bb: - retinfo.append(str(ins.medium_level_il)) - retinfo.append(str(ins.mapped_medium_level_il)) - retinfo.append(str(ins.value)) - retinfo.append(str(ins.possible_values)) - retinfo.append(str(ins.prefix_operands)) - retinfo.append(str(ins.postfix_operands)) - retinfo.append(str(ins.ssa_form)) - retinfo.append(str(ins.non_ssa_form)) - - return retinfo + retinfo.append("MLIL: " + str(ins.medium_level_il)) + retinfo.append("Mapped MLIL: " + str(ins.mapped_medium_level_il)) + retinfo.append("Value: " + str(ins.value)) + retinfo.append("Possible Values: " + str(ins.possible_values)) + retinfo.append("Prefix operands: " + str(ins.prefix_operands)) + retinfo.append("Postfix operands: " + str(ins.postfix_operands)) + retinfo.append("SSA form: " + str(ins.ssa_form)) + retinfo.append("Non-SSA form: " + str(ins.non_ssa_form)) + return fixOutput(retinfo) - def test_low_il_ssa(self): """LLIL ssa produced different output""" retinfo = [] - reg_list = [binja.SSARegister(i,1) for i in self.bv.arch.regs] - flag_list = [binja.SSAFlag(i,1) for i in self.bv.arch.flags] for func in self.bv.functions: func = func.low_level_il - for reg in reg_list: - retinfo.append(str(func.get_ssa_reg_definition(reg))) - retinfo.append(str(func.get_ssa_reg_uses(reg))) - retinfo.append(str(func.get_ssa_reg_value(reg))) - for flag in flag_list: - retinfo.append(str(func.get_ssa_flag_uses(flag))) - retinfo.append(str(func.get_ssa_flag_value(flag))) + for reg_name in self.bv.arch.regs: + reg = binja.SSARegister(reg_name, 1) + retinfo.append("Reg {} SSA definition: ".format(reg_name) + str(func.get_ssa_reg_definition(reg))) + retinfo.append("Reg {} SSA uses: ".format(reg_name) + str(func.get_ssa_reg_uses(reg))) + retinfo.append("Reg {} SSA value: ".format(reg_name) + str(func.get_ssa_reg_value(reg))) + for flag_name in self.bv.arch.flags: + flag = binja.SSAFlag(flag_name, 1) + retinfo.append("Flag {} SSA uses: ".format(flag_name) + str(func.get_ssa_flag_uses(flag))) + retinfo.append("Flag {} SSA value: ".format(flag_name) + str(func.get_ssa_flag_value(flag))) for bb in func.basic_blocks: for ins in bb: tempind = func.get_non_ssa_instruction_index(ins.instr_index) - retinfo.append(str(tempind)) - retinfo.append(str(func.get_ssa_instruction_index(tempind))) - retinfo.append(str(func.get_medium_level_il_instruction_index(ins.instr_index))) - retinfo.append(str(func.get_mapped_medium_level_il_instruction_index(ins.instr_index))) - - return retinfo - + retinfo.append("Non-SSA instruction index: " + str(tempind)) + retinfo.append("SSA instruction index: " + str(func.get_ssa_instruction_index(tempind))) + retinfo.append("MLIL instruction index: " + str(func.get_medium_level_il_instruction_index(ins.instr_index))) + retinfo.append("Mapped MLIL instruction index: " + str(func.get_mapped_medium_level_il_instruction_index(ins.instr_index))) + return fixOutput(retinfo) def test_med_il_instructions(self): """MLIL instructions produced different output""" @@ -153,22 +207,37 @@ class BinaryViewTestBuilder(Builder): for func in self.bv.functions: for bb in func.medium_level_il.basic_blocks: for ins in bb: - retinfo.append(str(ins.expr_type)) - retinfo.append(str(ins.low_level_il)) - retinfo.append(str(ins.value)) - retinfo.append(str(ins.possible_values)) - retinfo.append(str(ins.branch_dependence)) - retinfo.append(str(sorted([str(i) for i in ins.prefix_operands]))) - retinfo.append(str(sorted([str(i) for i in ins.postfix_operands]))) - retinfo.append(str(ins.ssa_form)) - retinfo.append(str(ins.non_ssa_form)) + retinfo.append("Expression type: " + str(ins.expr_type)) + retinfo.append("LLIL: " + str(ins.low_level_il)) + retinfo.append("Value: " + str(ins.value)) + retinfo.append("Possible values: " + str(ins.possible_values)) + retinfo.append("Branch dependence: " + str(sorted(ins.branch_dependence.items()))) - return retinfo + prefixList = [] + for i in ins.prefix_operands: + if isinstance(i, float) and 'e' in str(i): + prefixList.append(str(round(i, 21))) + elif isinstance(i, float): + prefixList.append(str(round(i, 11))) + else: + prefixList.append(str(i)) + retinfo.append("Prefix operands: " + str(sorted(prefixList))) + postfixList = [] + for i in ins.prefix_operands: + if isinstance(i, float) and 'e' in str(i): + postfixList.append(str(round(i, 21))) + elif isinstance(i, float): + postfixList.append(str(round(i, 11))) + else: + postfixList.append(str(i)) - + retinfo.append("Postfix operands: " + str(sorted(postfixList))) + retinfo.append("SSA form: " + str(ins.ssa_form)) + retinfo.append("Non-SSA form" + str(ins.non_ssa_form)) + return fixOutput(retinfo) def test_med_il_vars(self): - """"Function med_il_vars doesn't match""" + """Function med_il_vars doesn't match""" varlist = [] for func in self.bv.functions: func = func.medium_level_il @@ -176,14 +245,13 @@ class BinaryViewTestBuilder(Builder): for instruction in bb: instruction = instruction.ssa_form for var in (instruction.vars_read + instruction.vars_written): - #varlist.append((func.get_var_uses(var), func.get_var_definitions(var))) if hasattr(var, "var"): - varlist.append(str(func.get_ssa_var_definition(var)) + ' ' + \ - str(func.get_ssa_var_uses(var)) + ' ' + \ - str(func.get_ssa_var_value(var)) + ' ' + \ - str(instruction.get_ssa_var_possible_values(var)) + ' ' + \ - str(instruction.get_ssa_var_version)) - return varlist + varlist.append("SSA var definition: " + str(func.get_ssa_var_definition(var))) + varlist.append("SSA var uses: " + str(func.get_ssa_var_uses(var))) + varlist.append("SSA var value: " + str(func.get_ssa_var_value(var))) + varlist.append("SSA var possible values: " + fixSet(str(instruction.get_ssa_var_possible_values(var)))) + varlist.append("SSA var version: " + str(instruction.get_ssa_var_version)) + return fixOutput(varlist) def test_function_stack(self): """Function stack produced different output""" @@ -194,42 +262,35 @@ class BinaryViewTestBuilder(Builder): func.create_user_stack_var(0, binja.Type.int(4), "testuservar") func.create_auto_stack_var(4, binja.Type.int(4), "testautovar") - temp = [] - for i in func.stack_layout: - temp.append(str(i)) - funcinfo.append(str(temp)) + sl = func.stack_layout + for i in range(len(sl)): + funcinfo.append("Stack position {}: ".format(i) + str(sl[i])) - funcinfo.append(str(func.get_stack_contents_at(func.start + 0x10, 0, 0x10))) - funcinfo.append(str(func.get_stack_contents_after(func.start + 0x10, 0, 0x10))) - funcinfo.append(str(func.get_stack_var_at_frame_offset(0, 0))) + funcinfo.append("Stack content sample: " + str(func.get_stack_contents_at(func.start + 0x10, 0, 0x10))) + funcinfo.append("Stack content range sample: " + str(func.get_stack_contents_after(func.start + 0x10, 0, 0x10))) + funcinfo.append("Sample stack var: " + str(func.get_stack_var_at_frame_offset(0, 0))) func.delete_user_stack_var(0) func.delete_auto_stack_var(0) - return funcinfo def test_function_llil(self): """Function LLIL produced different output""" retinfo = [] - for func in self.bv.functions: for llilbb in func.llil_basic_blocks: - retinfo.append(str(llilbb)) + retinfo.append("LLIL basic block: " + str(llilbb)) for llilins in func.llil_instructions: - retinfo.append(str(llilins)) - + retinfo.append("LLIL instruction: " + str(llilins)) for mlilbb in func.mlil_basic_blocks: - retinfo.append(str(mlilbb)) + retinfo.append("MLIL basic block: " + str(mlilbb)) for mlilins in func.mlil_instructions: - retinfo.append(str(mlilins)) - + retinfo.append("MLIL instruction: " + str(mlilins)) for ins in func.instructions: - retinfo.append(str(ins)) - - return retinfo - + retinfo.append("Instruction: {}: ".format(hex(ins[1])) + ''.join([str(i) for i in ins[0]])) + return fixOutput(retinfo) def test_functions_attributes(self): - """"Function attributes don't match""" + """Function attributes don't match""" funcinfo = [] for func in self.bv.functions: func.comment = "testcomment " + func.name @@ -245,62 +306,67 @@ class BinaryViewTestBuilder(Builder): func.clobbered_regs = func.clobbered_regs func.set_user_instr_highlight(func.start, binja.highlight.HighlightColor(red=0xff, blue=0xff, green=0)) func.set_auto_instr_highlight(func.start, binja.highlight.HighlightColor(red=0xff, blue=0xfe, green=0)) - - funcinfo.append(str([str(i) for i in func.vars])) - funcinfo.append(str(func.indirect_branches)) - funcinfo.append(str(func.session_data)) - funcinfo.append(len(func.analysis_performance_info)) - funcinfo.append(str(func.clobbered_regs)) - funcinfo.append(str(func.explicitly_defined_type)) - funcinfo.append(str(func.needs_update)) - funcinfo.append(len(func.lifted_il)) - funcinfo.append(str(func.global_pointer_value)) - funcinfo.append(str(func.comment)) - funcinfo.append(str(func.too_large)) - funcinfo.append(str(func.analysis_skipped)) - funcinfo.append(str(func.get_low_level_il_at(func.start))) - funcinfo.append(str(func.get_low_level_il_exits_at(func.start+0x100))) - funcinfo.append(str(func.get_regs_read_by(func.start))) - funcinfo.append(str(func.get_regs_written_by(func.start))) - funcinfo.append(str(func.get_stack_vars_referenced_by(func.start))) - funcinfo.append(str(func.get_constants_referenced_by(func.start))) - funcinfo.append(str(func.get_lifted_il_at(func.start))) - funcinfo.append(str(func.get_flags_read_by_lifted_il_instruction(0))) - funcinfo.append(str(func.get_flags_written_by_lifted_il_instruction(0))) - funcinfo.append(str(func.create_graph())) - funcinfo.append(str(func.get_indirect_branches_at(func.start+0x10))) - funcinfo.append(str(func.get_block_annotations(func.start))) - funcinfo.append(str(func.get_basic_block_at(func.start))) - funcinfo.append(str(func.get_instr_highlight(func.start))) - funcinfo.append(str(func.get_type_tokens())) + for var in func.vars: + funcinfo.append("Function {} var: ".format(func.name) + str(var)) - return funcinfo + for branch in func.indirect_branches: + funcinfo.append("Function {} indirect branch: ".format(func.name) + str(branch)) + funcinfo.append("Function {} session data: ".format(func.name) + str(func.session_data)) + funcinfo.append("Function {} analysis perf length: ".format(func.name) + str(len(func.analysis_performance_info))) + for cr in func.clobbered_regs: + funcinfo.append("Function {} clobbered reg: ".format(func.name) + str(cr)) + funcinfo.append("Function {} explicitly defined type: ".format(func.name) + str(func.explicitly_defined_type)) + funcinfo.append("Function {} needs update: ".format(func.name) + str(func.needs_update)) + funcinfo.append("Function {} global pointer value: ".format(func.name) + str(func.global_pointer_value)) + funcinfo.append("Function {} comment: ".format(func.name) + str(func.comment)) + funcinfo.append("Function {} too large: ".format(func.name) + str(func.too_large)) + funcinfo.append("Function {} analysis skipped: ".format(func.name) + str(func.analysis_skipped)) + funcinfo.append("Function {} first ins LLIL: ".format(func.name) + str(func.get_low_level_il_at(func.start))) + funcinfo.append("Function {} LLIL exit test: ".format(func.name) + str(func.get_low_level_il_exits_at(func.start+0x100))) + funcinfo.append("Function {} regs read test: ".format(func.name) + str(func.get_regs_read_by(func.start))) + funcinfo.append("Function {} regs written test: ".format(func.name) + str(func.get_regs_written_by(func.start))) + funcinfo.append("Function {} stack var test: ".format(func.name) + str(func.get_stack_vars_referenced_by(func.start))) + funcinfo.append("Function {} constant reference test: ".format(func.name) + str(func.get_constants_referenced_by(func.start))) + funcinfo.append("Function {} first ins lifted IL: ".format(func.name) + str(func.get_lifted_il_at(func.start))) + funcinfo.append("Function {} flags read by lifted IL ins: ".format(func.name) + str(func.get_flags_read_by_lifted_il_instruction(0))) + funcinfo.append("Function {} flags written by lifted IL ins: ".format(func.name) + str(func.get_flags_written_by_lifted_il_instruction(0))) + funcinfo.append("Function {} create graph: ".format(func.name) + str(func.create_graph())) + funcinfo.append("Function {} indirect branches test: ".format(func.name) + str(func.get_indirect_branches_at(func.start+0x10))) + funcinfo.append("Function {} test instr highlight: ".format(func.name) + str(func.get_instr_highlight(func.start))) + for token in func.get_type_tokens(): + token = str(token) + token = remove_low_confidence(token) + funcinfo.append("Function {} type token: ".format(func.name) + str(token)) + return fixOutput(funcinfo) def test_BinaryView(self): """BinaryView produced different results""" retinfo = [] - retinfo += [str(i[1]) for i in self.bv.types.items()] - retinfo.append(str(self.bv.segments)) - retinfo.append(str(self.bv.sections)) - retinfo.append(str(self.bv.allocated_ranges)) - retinfo.append(str(self.bv.session_data)) + for type in self.bv.types.items(): + retinfo.append("BV Type: " + str(type)) + for segment in sorted([str(i) for i in self.bv.segments]): + retinfo.append("BV segment: " + str(segment)) + for section in sorted(self.bv.sections): + retinfo.append("BV section: " + str(section)) + for allrange in self.bv.allocated_ranges: + retinfo.append("BV allocated range: " + str(allrange)) + retinfo.append("Session Data: " + str(self.bv.session_data)) for var in self.bv.data_vars: - retinfo.append(str(var)) - retinfo.append(str(self.bv.entry_function)) + retinfo.append("BV data var: " + str(var)) + retinfo.append("BV Entry function: " + str(self.bv.entry_function)) for i in self.bv: - retinfo.append(str(i)) - retinfo.append(hex(self.bv.entry_point)) - retinfo.append(hex(self.bv.start)) - retinfo.append("length: " + hex(len(self.bv))) - return retinfo - + retinfo.append("BV function: " + str(i)) + retinfo.append("BV entry point: " + hex(self.bv.entry_point)) + retinfo.append("BV start: " + hex(self.bv.start)) + retinfo.append("BV length: " + hex(len(self.bv))) + return fixOutput(retinfo) class TestBuilder(Builder): - """ The TestBuilder is for tests that need to be checked against + """ The TestBuilder is for tests that need to be checked againsttest_BinaryView stored oracle data that isn't from a binary. These test are generated on your local machine then run again on the build machine to verify correctness. @@ -312,74 +378,133 @@ class TestBuilder(Builder): def test_BinaryViewType_list(self): """BinaryViewType list doesnt match""" - return [x.name for x in binja.BinaryViewType.list] + return ["BinaryViewType: " + x.name for x in binja.BinaryViewType.list] def test_Architecture_list(self): """Architecture list doesnt match""" - return [x.name for x in binja.Architecture.list] + return ["Arch name: " + x.name for x in binja.Architecture.list] def test_Assemble(self): """unexpected assemble result""" result = [] # success cases - result.append(binja.Architecture["x86"].assemble("xor eax, eax")) - result.append(binja.Architecture["x86_64"].assemble("xor rax, rax")) - result.append(binja.Architecture["mips32"].assemble("move $ra, $zero")) - result.append(binja.Architecture["mipsel32"].assemble("move $ra, $zero")) - result.append(binja.Architecture["armv7"].assemble("str r2, [sp, #-0x4]!")) - result.append(binja.Architecture["aarch64"].assemble("mov x0, x0")) - result.append(binja.Architecture["thumb2"].assemble("ldr r4, [r4]")) - result.append(binja.Architecture["thumb2eb"].assemble("ldr r4, [r4]")) + + strResult = binja.Architecture["x86"].assemble("xor eax, eax") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("x86 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("x86 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["x86_64"].assemble("xor rax, rax") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("x86_64 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("x86_64 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["mips32"].assemble("move $ra, $zero") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("mips32 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("mips32 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["mipsel32"].assemble("move $ra, $zero") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("mipsel32 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("mipsel32 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["armv7"].assemble("str r2, [sp, #-0x4]!") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("armv7 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("armv7 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["aarch64"].assemble("mov x0, x0") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("aarch64 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("aarch64 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["thumb2"].assemble("ldr r4, [r4]") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("thumb2 assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("thumb2 assembly: " + repr(str(strResult))) + strResult = binja.Architecture["thumb2eb"].assemble("ldr r4, [r4]") + if sys.version_info.major == 3 and not strResult[0] is None: + result.append("thumb2eb assembly: " + "'" + str(strResult)[2:-1] + "'") + else: + result.append("thumb2eb assembly: " + repr(str(strResult))) + # fail cases - result.append(binja.Architecture["x86"].assemble("thisisnotaninstruction")) - result.append(binja.Architecture["x86_64"].assemble("thisisnotaninstruction")) - result.append(binja.Architecture["mips32"].assemble("thisisnotaninstruction")) - result.append(binja.Architecture["mipsel32"].assemble("thisisnotaninstruction")) - result.append(binja.Architecture["armv7"].assemble("thisisnotaninstruction")) - result.append(binja.Architecture["aarch64"].assemble("thisisnotaninstruction")) - result.append(binja.Architecture["thumb2"].assemble("thisisnotaninstruction")) - result.append(binja.Architecture["thumb2eb"].assemble("thisisnotaninstruction")) + try: + strResult = binja.Architecture["x86"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'x86'") + try: + strResult = binja.Architecture["x86_64"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'x86_64'") + try: + strResult = binja.Architecture["mips32"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'mips32'") + try: + strResult = binja.Architecture["mipsel32"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'mipsel32'") + try: + strResult = binja.Architecture["armv7"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'armv7'") + try: + strResult = binja.Architecture["aarch64"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'aarch64'") + try: + strResult = binja.Architecture["thumb2"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'thumb2'") + try: + strResult = binja.Architecture["thumb2eb"].assemble("thisisnotaninstruction") + except ValueError: + result.append("Assemble Failed As Expected; 'thisisnotaninstruction' is not an instruction on 'thumb2eb'") return result def test_Architecture(self): """Architecture failure""" if not os.path.exists(os.path.join(os.path.expanduser("~"), '.binaryninja', 'plugins', 'nes.py')): - return + return [""] retinfo = [] file_name = os.path.join(self.test_store, "..", "pwnadventurez.nes") bv = binja.BinaryViewType["NES Bank 0"].open(file_name) - - retinfo.append(str([str(i) for i in bv.platform.arch.calling_conventions])) - retinfo.append(str(bv.platform.arch.full_width_regs)) + + for i in bv.platform.arch.calling_conventions: + retinfo.append("Custom arch calling convention: " + str(i)) + for i in bv.platform.arch.full_width_regs: + retinfo.append("Custom arch full width reg: " + str(i)) reg = binja.RegisterValue() - retinfo.append(str(reg.entry_value(bv.platform.arch, 'x'))) - retinfo.append(str(reg.constant(0xfe))) - retinfo.append(str(reg.constant_ptr(0xcafebabe))) - retinfo.append(str(reg.stack_frame_offset(0x10))) - retinfo.append(str(reg.imported_address(0xdeadbeef))) - retinfo.append(str(reg.return_address())) + retinfo.append("Reg entry value: " + str(reg.entry_value(bv.platform.arch, 'x'))) + retinfo.append("Reg constant: " + str(reg.constant(0xfe))) + retinfo.append("Reg constant pointer: " + str(reg.constant_ptr(0xcafebabe))) + retinfo.append("Reg stack frame offset: " + str(reg.stack_frame_offset(0x10))) + retinfo.append("Reg imported address: " + str(reg.imported_address(0xdeadbeef))) + retinfo.append("Reg return address: " + str(reg.return_address())) bv.update_analysis_and_wait() for func in bv.functions: for bb in func.low_level_il.basic_blocks: for ins in bb: - retinfo.append(str(bv.platform.arch.get_instruction_info(0x10, ins.address))) - retinfo.append(str(bv.platform.arch.get_instruction_text(0x10, ins.address))) - retinfo.append(str(ins)) + retinfo.append("Instruction info: " + str(bv.platform.arch.get_instruction_info(0x10, ins.address))) + retinfo.append("Instruction test: " + str(bv.platform.arch.get_instruction_text(0x10, ins.address))) + retinfo.append("Instruction: " + str(ins)) return retinfo - - def test_Function(self): """Function produced different result""" inttype = binja.Type.int(4) testfunction = binja.Type.function(inttype, [inttype, inttype, inttype]) - return [str(testfunction.parameters), str(testfunction.pointer(binja.Architecture["x86"], testfunction))] + return ["Test_function params: " + str(testfunction.parameters), "Test_function pointer: " + str(testfunction.pointer(binja.Architecture["x86"], testfunction))] def test_Struct(self): """Struct produced different result""" + retinfo = [] inttype = binja.Type.int(4) struct = binja.Structure() struct.a = 1 @@ -387,22 +512,23 @@ class TestBuilder(Builder): struct.append(inttype) struct.replace(0, inttype) struct.remove(1) - retinfo = [str(i) for i in struct.members] - retinfo += [struct.width] + for i in struct.members: + retinfo.append("Struct member: " + str(i)) + retinfo.append("Struct width: " + str(struct.width)) struct.width = 16 - retinfo += [struct.width] - retinfo += [struct.alignment] + retinfo.append("Struct width after adjustment: " + str(struct.width)) + retinfo.append("Struct alignment: " + str(struct.alignment)) struct.alignment = 8 - retinfo += [struct.alignment] - retinfo += [struct.packed] + retinfo.append("Struct alignment after adjustment: " + str(struct.alignment)) + retinfo.append("Struct packed: " + str(struct.packed)) struct.packed = 1 - retinfo += [struct.packed] - retinfo += [struct.type] - retinfo = [str(i) for i in retinfo] + retinfo.append("Struct packed after adjustment: " + str(struct.packed)) + retinfo.append("Struct type: " + str(struct.type)) return retinfo def test_Enumeration(self): """Enumeration produced different result""" + retinfo = [] inttype = binja.Type.int(4) enum = binja.Enumeration() enum.a = 1 @@ -410,16 +536,15 @@ class TestBuilder(Builder): enum.append("b", 2) enum.replace(0, "a", 2) enum.remove(0) - retinfo = [str(enum)] - retinfo += [str((enum == enum) and not (enum != enum))] + retinfo.append(str(enum)) + retinfo.append(str((enum == enum) and not (enum != enum))) return retinfo def test_Types(self): """Types produced different result""" - - file_name = os.path.join(self.test_store, "helloworld") + file_name = self.unpackage_file("helloworld") bv = binja.BinaryViewType.get_view_of_file(file_name) - + preprocessed = binja.preprocess_source(""" #ifdef nonexistant int foo = 1; @@ -429,70 +554,64 @@ class TestBuilder(Builder): long long bar1 = 2; #endif """) - source = '\n'.join([i.decode("utf-8") for i in preprocessed[0].split('\n') if not b"#line" in i and len(i) > 0]) + source = '\n'.join([i.decode('charmap') for i in preprocessed[0].split(b'\n') if not b'#line' in i and len(i) > 0]) typelist = bv.platform.parse_types_from_source(source) inttype = binja.Type.int(4) tokens = inttype.get_tokens() + inttype.get_tokens_before_name() + inttype.get_tokens_after_name() namedtype = binja.NamedTypeReference() - retinfo = [[str(i) for i in typelist.variables.popitem()][::-1] for i in range(len(typelist.variables))] - retinfo += [str(namedtype)] - - equalcheck = (inttype == inttype) and not (inttype != inttype) - retinfo += [str(equalcheck)] - return retinfo + retinfo = [] + for i in range(len(typelist.variables)): + for j in typelist.variables.popitem(): + retinfo.append("Type: " + str(j)) + retinfo.append("Named Type: " + str(namedtype)) + retinfo.append("Type equality: " + str((inttype == inttype) and not (inttype != inttype))) + return retinfo def test_Plugin_bin_info(self): """print_syscalls plugin produced different result""" - - file_name = os.path.join(self.test_store, "helloworld") - self.unpackage_file(file_name) - result = subprocess.Popen(["python", os.path.join(self.examples_dir, "bin_info.py"), file_name], stdout=subprocess.PIPE).communicate()[0] + file_name = self.unpackage_file("helloworld") + bin_info_path = os.path.join(os.path.dirname(__file__), '..', 'python', 'examples', 'bin_info.py') + result = subprocess.Popen(["python", bin_info_path, file_name], stdout=subprocess.PIPE).communicate()[0] # normalize line endings and path sep - return [result.replace(b"\\", b"/").replace(b"\r\n", b"\n")] + return [line for line in result.replace(b"\\", b"/").replace(b"\r\n", b"\n").decode("charmap").split("\n")] def test_linear_disassembly(self): """linear_disassembly produced different result""" - file = os.path.join(self.test_store, "helloworld") - self.unpackage_file(file) - bv = binja.BinaryViewType['ELF'].open(file) - settings = binja.DisassemblySettings() + file_name = self.unpackage_file("helloworld") + bv = binja.BinaryViewType['ELF'].open(file_name) disass = bv.linear_disassembly - res = [] + retinfo = [] for i in disass: - res.append(str(i)) - #return res + i = str(i) + i = remove_low_confidence(i) + retinfo.append(i) + return retinfo def test_partial_register_dataflow(self): """partial_register_dataflow produced different results""" - - file_name = os.path.join(self.test_store, "partial_register_dataflow") - self.unpackage_file(file_name) + file_name = self.unpackage_file("partial_register_dataflow") result = [] - try: - reg_list = ['ch', 'cl', 'ah', 'edi', 'al', 'cx', 'ebp', 'ax', 'edx', 'ebx', 'esp', 'esi', 'dl', 'dh', 'di', 'bl', 'bh', 'eax', 'dx', 'bx', 'ecx', 'sp', 'si'] - bv = binja.BinaryViewType.get_view_of_file(file_name) - for func in bv.functions: - llil = func.low_level_il - for i in range(0, llil.__len__()-1): - result.append(["LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_reg_value(x)).replace('L', '') for x in reg_list]) - result.append(["LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_possible_reg_values(x)).replace('L', '') for x in reg_list]) - result.append(["LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_reg_value_after(x)).replace('L', '') for x in reg_list]) - result.append(["LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_possible_reg_values_after(x)).replace('L', '') for x in reg_list]) - bv.file.close() - del bv - finally: - os.unlink(file_name) - + reg_list = ['ch', 'cl', 'ah', 'edi', 'al', 'cx', 'ebp', 'ax', 'edx', 'ebx', 'esp', 'esi', 'dl', 'dh', 'di', 'bl', 'bh', 'eax', 'dx', 'bx', 'ecx', 'sp', 'si'] + bv = binja.BinaryViewType.get_view_of_file(file_name) + for func in bv.functions: + llil = func.low_level_il + for i in range(0, llil.__len__()-1): + for x in reg_list: + result.append("LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_reg_value(x)).replace('L', '')) + result.append("LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_possible_reg_values(x)).replace('L', '')) + result.append("LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_reg_value_after(x)).replace('L', '')) + result.append("LLIL:" + str(i).replace('L', '') + ":" + x + ":" + str(llil[i].get_possible_reg_values_after(x)).replace('L', '')) + bv.file.close() + del bv return result def test_low_il_stack(self): """LLIL stack produced different output""" - file_name = os.path.join(self.test_store, "jumptable_reordered") - self.unpackage_file(file_name) + file_name = self.unpackage_file("jumptable_reordered") bv = binja.BinaryViewType.get_view_of_file(file_name) reg_list = ['ch', 'cl', 'ah', 'edi', 'al', 'cx', 'ebp', 'ax', 'edx', 'ebx', 'esp', 'esi', 'dl', 'dh', 'di', 'bl', 'bh', 'eax', 'dx', 'bx', 'ecx', 'sp', 'si'] flag_list = ['c', 'p', 'a', 'z', 's', 'o'] @@ -500,26 +619,20 @@ class TestBuilder(Builder): for func in bv.functions: for bb in func.low_level_il.basic_blocks: for ins in bb: - retinfo.append(str(ins.get_stack_contents(0,1))) - retinfo.append(str(ins.get_stack_contents_after(0,1))) - retinfo.append(str(ins.get_possible_stack_contents(0,1))) - retinfo.append(str(ins.get_possible_stack_contents_after(0,1))) - - + retinfo.append("LLIL first stack element: " + str(ins.get_stack_contents(0,1))) + retinfo.append("LLIL second stack element: " + str(ins.get_stack_contents_after(0,1))) + retinfo.append("LLIL possible first stack element: " + str(ins.get_possible_stack_contents(0,1))) + retinfo.append("LLIL possible second stack element: " + str(ins.get_possible_stack_contents_after(0,1))) for flag in flag_list: - retinfo.append(str(ins.get_flag_value(flag))) - retinfo.append(str(ins.get_flag_value_after(flag))) - retinfo.append(str(ins.get_possible_flag_values(flag))) - retinfo.append(str(ins.get_possible_flag_values_after(flag))) - - os.unlink(file_name) - - return retinfo + retinfo.append("LLIL flag {} value at: ".format(flag, hex(ins.address)) + str(ins.get_flag_value(flag))) + retinfo.append("LLIL flag {} value after {}: ".format(flag, hex(ins.address)) + str(ins.get_flag_value_after(flag))) + retinfo.append("LLIL flag {} possible value at {}: ".format(flag, hex(ins.address)) + str(ins.get_possible_flag_values(flag))) + retinfo.append("LLIL flag {} possible value after {}: ".format(flag, hex(ins.address)) + str(ins.get_possible_flag_values_after(flag))) + return fixOutput(retinfo) def test_med_il_stack(self): """MLIL stack produced different output""" - file_name = os.path.join(self.test_store, "jumptable_reordered") - self.unpackage_file(file_name) + file_name = self.unpackage_file("jumptable_reordered") bv = binja.BinaryViewType.get_view_of_file(file_name) reg_list = ['ch', 'cl', 'ah', 'edi', 'al', 'cx', 'ebp', 'ax', 'edx', 'ebx', 'esp', 'esi', 'dl', 'dh', 'di', 'bl', 'bh', 'eax', 'dx', 'bx', 'ecx', 'sp', 'si'] flag_list = ['c', 'p', 'a', 'z', 's', 'o'] @@ -527,159 +640,108 @@ class TestBuilder(Builder): for func in bv.functions: for bb in func.medium_level_il.basic_blocks: for ins in bb: - retinfo.append(str(ins.get_var_for_stack_location(0))) - retinfo.append(str(ins.get_stack_contents(0, 0x10))) - retinfo.append(str(ins.get_stack_contents_after(0, 0x10))) - retinfo.append(str(ins.get_possible_stack_contents(0, 0x10))) - retinfo.append(str(ins.get_possible_stack_contents_after(0, 0x10))) - - for reg in reg_list: - retinfo.append(str(ins.get_var_for_reg(reg))) - retinfo.append(str(ins.get_reg_value(reg))) - retinfo.append(str(ins.get_reg_value_after(reg))) - retinfo.append(str(ins.get_possible_reg_values(reg))) - retinfo.append(str(ins.get_possible_reg_values_after(reg))) + retinfo.append("MLIL stack begin var: " + str(ins.get_var_for_stack_location(0))) + retinfo.append("MLIL first stack element: " + str(ins.get_stack_contents(0, 1))) + retinfo.append("MLIL second stack element: " + str(ins.get_stack_contents_after(0, 1))) + retinfo.append("MLIL possible first stack element: " + str(ins.get_possible_stack_contents(0, 1))) + retinfo.append("MLIL possible second stack element: " + str(ins.get_possible_stack_contents_after(0, 1))) + for reg in reg_list: + retinfo.append("MLIL reg {} var at {}: ".format(reg, hex(ins.address)) + str(ins.get_var_for_reg(reg))) + retinfo.append("MLIL reg {} value at {}: ".format(reg, hex(ins.address)) + str(ins.get_reg_value(reg))) + retinfo.append("MLIL reg {} value after {}: ".format(reg, hex(ins.address)) + str(ins.get_reg_value_after(reg))) + retinfo.append("MLIL reg {} possible value at {}: ".format(reg, hex(ins.address)) + fixSet(str(ins.get_possible_reg_values(reg)))) + retinfo.append("MLIL reg {} possible value after {}: ".format(reg, hex(ins.address)) + fixSet(str(ins.get_possible_reg_values_after(reg)))) for flag in flag_list: - retinfo.append(str(ins.get_flag_value(flag))) - retinfo.append(str(ins.get_flag_value_after(flag))) - retinfo.append(str(ins.get_possible_flag_values(flag))) - retinfo.append(str(ins.get_possible_flag_values_after(flag))) - - os.unlink(file_name) - - return retinfo - + retinfo.append("MLIL flag {} value at: ".format(flag, hex(ins.address)) + str(ins.get_flag_value(flag))) + retinfo.append("MLIL flag {} value after {}: ".format(flag, hex(ins.address)) + str(ins.get_flag_value_after(flag))) + retinfo.append("MLIL flag {} possible value at {}: ".format(flag, hex(ins.address)) + fixSet(str(ins.get_possible_flag_values(flag)))) + retinfo.append("MLIL flag {} possible value after {}: ".format(flag, hex(ins.address)) + fixSet(str(ins.get_possible_flag_values(flag)))) + return fixOutput(retinfo) def test_events(self): """Event failure""" - file_name = os.path.join(self.test_store, "helloworld") - self.unpackage_file(file_name) + file_name = self.unpackage_file("helloworld") bv = binja.BinaryViewType['ELF'].open(file_name) + bv.update_analysis_and_wait() results = [] - def simple_complete(self): results.append("analysis complete") - evt = binja.AnalysisCompletionEvent(bv, simple_complete) class NotifyTest(binja.BinaryDataNotification): - def data_written(self, view, offset, length): - def data_written_complete(self): - results.append("data written {0} {1}".format(offset, length)) - evt = binja.AnalysisCompletionEvent(bv, data_written_complete) + results.append("data written: offset {0} length {1}".format(hex(offset), hex(length))) def data_inserted(self, view, offset, length): - def data_inserted_complete(self): - results.append("data inserted {0} {1}".format(offset, length)) - evt = binja.AnalysisCompletionEvent(bv, data_inserted_complete) + results.append("data inserted: offset {0} length {1}".format(hex(offset), hex(length))) def data_removed(self, view, offset, length): - def data_removed_complete(self): - results.append("data removed {0} {1}".format(offset, length)) - evt = binja.AnalysisCompletionEvent(bv, data_removed_complete) + results.append("data removed: offset {0} length {1}".format(hex(offset), hex(length))) def function_added(self, view, func): - def function_added_complete(self): - results.append("function added {0}".format(func.name)) - evt = binja.AnalysisCompletionEvent(bv, function_added_complete) + results.append("function added: {0}".format(func.name)) def function_removed(self, view, func): - def function_removed_complete(self): - results.append("function removed {0}".format(func.name)) - evt = binja.AnalysisCompletionEvent(bv, function_removed_complete) - - def function_updated(self, view, func): - def function_updated_complete(self): - results.append("function updated {0}".format(func.name)) - evt = binja.AnalysisCompletionEvent(bv, function_updated_complete) - - def function_update_requested(self, view, func): - def function_update_requested_complete(self): - results.append("function update requested {0}".format(func.name)) - evt = binja.AnalysisCompletionEvent(bv, function_update_requested_complete) + results.append("function removed: {0}".format(func.name)) def data_var_added(self, view, var): - def data_var_added_complete(self): - results.append("data var added {0}".format(var.name)) - evt = binja.AnalysisCompletionEvent(bv, data_var_added_complete) + results.append("data var added: {0}".format(hex(var.address))) def data_var_removed(self, view, var): - def data_var_removed_complete(self): - results.append("data var removed {0}".format(var.name)) - evt = binja.AnalysisCompletionEvent(bv, data_var_removed_complete) - - def data_var_updated(self, view, var): - def data_var_updated_complete(self): - results.append("data var updated {0}".format(var.name)) - evt = binja.AnalysisCompletionEvent(bv, data_var_updated_complete) + results.append("data var removed: {0}".format(hex(var.address))) def string_found(self, view, string_type, offset, length): - def string_found_complete(self): - results.append("string found {0} {1}".format(offset, length)) - evt = binja.AnalysisCompletionEvent(bv, string_found_complete) + results.append("string found: offset {0} length {1}".format(hex(offset), hex(length))) def string_removed(self, view, string_type, offset, length): - def string_removed_complete(self): - results.append("string removed {0} {1}".format(offset, length)) - evt = binja.AnalysisCompletionEvent(bv, string_removed_complete) + results.append("string removed: offset {0} length {1}".format(hex(offset), hex(length))) def type_defined(self, view, name, type): - def type_defined_complete(self): - results.append("type defined {0} {1}".format(name)) - evt = binja.AnalysisCompletionEvent(bv, type_defined_complete) + results.append("type defined: {0}".format(name)) def type_undefined(self, view, name, type): - def type_undefined_complete(self): - results.append("type undefined {0} {1}".format(name)) - evt = binja.AnalysisCompletionEvent(bv, type_undefined_complete) - + results.append("type undefined: {0}".format(name)) test = NotifyTest() bv.register_notification(test) sacrificial_addr = 0x84fc - type, name = bv.parse_type_string("int foo") type_id = type.generate_auto_type_id("source", name) + bv.define_type(type_id, name, type) bv.undefine_type(type_id) - bv.insert(sacrificial_addr, "AAAA") + bv.update_analysis_and_wait() + + bv.insert(sacrificial_addr, b"AAAA") + bv.update_analysis_and_wait() + bv.define_data_var(sacrificial_addr, binja.types.Type.int(4)) + bv.update_analysis_and_wait() - bv.write(sacrificial_addr, "BBBB") + bv.write(sacrificial_addr, b"BBBB") + bv.update_analysis_and_wait() bv.add_function(sacrificial_addr) + bv.update_analysis_and_wait() + bv.remove_function(bv.get_function_at(sacrificial_addr)) + bv.update_analysis_and_wait() bv.undefine_data_var(sacrificial_addr) - bv.remove(sacrificial_addr, 4) - bv.update_analysis_and_wait() - - bv.unregister_notification(test) - - return str(sorted(results)) - def unpackage(self, fileName): - testname = None - with zipfile.ZipFile(fileName, "r") as zf: - testname = zf.namelist()[0] - zf.extractall() + bv.remove(sacrificial_addr, 4) + bv.update_analysis_and_wait() - if not os.path.exists(testname + ".pkl"): - return None, None - binary_oracle = pickle.load(open(testname + ".pkl", "rb")) - return binary_oracle.oracle_test_data, testname + bv.unregister_notification(test) - def cleanup_package(self, fileName): - if fileName.endswith(".zip"): - os.unlink(fileName[:-4]) + return fixOutput(sorted(results)) class VerifyBuilder(Builder): @@ -690,6 +752,7 @@ class VerifyBuilder(Builder): - Function doc string used as 'on error' message - Should return: boolean """ + def __init__(self, test_store): super(VerifyBuilder, self).__init__(test_store) @@ -709,10 +772,9 @@ class VerifyBuilder(Builder): # - Save the database # - Restore the datbase # - Validate that the modifications are present - file = os.path.join(self.test_store, "helloworld") - self.unpackage_file(file) + file_name = self.unpackage_file("helloworld") try: - bv = binja.BinaryViewType['ELF'].open(file) + bv = binja.BinaryViewType['ELF'].open(file_name) bv.update_analysis_and_wait() # Make some modifications to the binary view @@ -727,10 +789,7 @@ class VerifyBuilder(Builder): bv.create_database(temp_name) bv.file.close() del bv - finally: - os.unlink(file) - try: bv = binja.FileMetadata(temp_name).open_existing_database(temp_name).get_view_of_type('ELF') bv.update_analysis_and_wait() bndb_functions = self.get_functions(bv) @@ -741,4 +800,3 @@ class VerifyBuilder(Builder): return [str(functions == bndb_functions and comments == bndb_comments)] finally: os.unlink(temp_name) - diff --git a/suite/unit.py b/suite/unit.py deleted file mode 100644 index 993daf41..00000000 --- a/suite/unit.py +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env python -# This is an auto generated unit test file do not edit directly -import os -import unittest -import pickle -import zipfile -import testcommon -import binaryninja -import api_test -import difflib - - - -class TestBinaryNinjaAPI(unittest.TestCase): - @classmethod - def setUpClass(self): - self.builder = testcommon.TestBuilder("suite/binaries/test_corpus") - try: - #Python 2 does not have the encodings option - self.oracle_test_data = pickle.load(open(os.path.join("suite", "oracle.pkl"), "rUb"), errors="ignore") - except TypeError: - self.oracle_test_data = pickle.load(open(os.path.join("suite", "oracle.pkl"), "rU")) - self.verifybuilder = testcommon.VerifyBuilder("suite/binaries/test_corpus") - - def run_binary_test(self, testfile): - testname = None - with zipfile.ZipFile(testfile, "r") as zf: - testname = zf.namelist()[0] - zf.extractall() - - self.assertTrue(os.path.exists(testname + ".pkl"), "Test pickle doesn't exist") - try: - #Python 2 does not have the encodings option - binary_oracle = pickle.load(open(testname + ".pkl", "rUb"), errors="ignore") - except TypeError: - binary_oracle = pickle.load(open(testname + ".pkl", "rU")) - - test_builder = testcommon.BinaryViewTestBuilder(testname, "suite/binaries/test_corpus") - for method in test_builder.methods(): - test = getattr(test_builder, method)() - oracle = binary_oracle[method] - if test == oracle: - continue - - result = getattr(test_builder, method).__doc__ - result += ":\n" - d = difflib.Differ(charjunk=difflib.IS_CHARACTER_JUNK) - skipped_lines = 0 - for delta in d.compare(test, oracle): - if delta[0] == ' ': - skipped_lines += 1 - continue - if skipped_lines > 0: - result += "<---" + str(skipped_lines) + ' same lines--->\n' - skipped_lines = 0 - delta = delta.replace('\n', '') - result += delta + '\n' - self.assertTrue(False, result) - os.unlink(testname) - - def test_Architecture(self): - oracle = self.oracle_test_data['test_Architecture'] - test = self.builder.test_Architecture() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Architecture.__doc__, oracle, test)) - - def test_Architecture_list(self): - oracle = self.oracle_test_data['test_Architecture_list'] - test = self.builder.test_Architecture_list() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Architecture_list.__doc__, oracle, test)) - - def test_Assemble(self): - oracle = self.oracle_test_data['test_Assemble'] - test = self.builder.test_Assemble() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Assemble.__doc__, oracle, test)) - - def test_BinaryViewType_list(self): - oracle = self.oracle_test_data['test_BinaryViewType_list'] - test = self.builder.test_BinaryViewType_list() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_BinaryViewType_list.__doc__, oracle, test)) - - def test_Enumeration(self): - oracle = self.oracle_test_data['test_Enumeration'] - test = self.builder.test_Enumeration() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Enumeration.__doc__, oracle, test)) - - def test_Function(self): - oracle = self.oracle_test_data['test_Function'] - test = self.builder.test_Function() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Function.__doc__, oracle, test)) - - def test_Plugin_bin_info(self): - oracle = self.oracle_test_data['test_Plugin_bin_info'] - test = self.builder.test_Plugin_bin_info() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Plugin_bin_info.__doc__, oracle, test)) - - def test_Struct(self): - oracle = self.oracle_test_data['test_Struct'] - test = self.builder.test_Struct() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Struct.__doc__, oracle, test)) - - def test_Types(self): - oracle = self.oracle_test_data['test_Types'] - test = self.builder.test_Types() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_Types.__doc__, oracle, test)) - - def test_events(self): - oracle = self.oracle_test_data['test_events'] - test = self.builder.test_events() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_events.__doc__, oracle, test)) - - def test_linear_disassembly(self): - oracle = self.oracle_test_data['test_linear_disassembly'] - test = self.builder.test_linear_disassembly() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_linear_disassembly.__doc__, oracle, test)) - - def test_low_il_stack(self): - oracle = self.oracle_test_data['test_low_il_stack'] - test = self.builder.test_low_il_stack() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_low_il_stack.__doc__, oracle, test)) - - def test_med_il_stack(self): - oracle = self.oracle_test_data['test_med_il_stack'] - test = self.builder.test_med_il_stack() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_med_il_stack.__doc__, oracle, test)) - - def test_partial_register_dataflow(self): - oracle = self.oracle_test_data['test_partial_register_dataflow'] - test = self.builder.test_partial_register_dataflow() - self.assertTrue(oracle == test, "%s:\n'%s' does not equal\n'%s'" % (self.builder.test_partial_register_dataflow.__doc__, oracle, test)) - - def test_verify_BNDB_round_trip(self): - self.assertTrue(self.verifybuilder.test_verify_BNDB_round_trip(), self.test_verify_BNDB_round_trip.__doc__) - - def test_binary___aliased_jumptable(self): - self.run_binary_test('suite/binaries/test_corpus/aliased_jumptable.zip') - - def test_binary___byte_jump_table(self): - self.run_binary_test('suite/binaries/test_corpus/byte_jump_table.zip') - - def test_binary___duff(self): - self.run_binary_test('suite/binaries/test_corpus/duff.zip') - - def test_binary___helloworld(self): - self.run_binary_test('suite/binaries/test_corpus/helloworld.zip') - - def test_binary___helloworld_armeb(self): - self.run_binary_test('suite/binaries/test_corpus/helloworld_armeb.zip') - - def test_binary___integer_test(self): - self.run_binary_test('suite/binaries/test_corpus/integer_test.zip') - - def test_binary___interprocedural_alias(self): - self.run_binary_test('suite/binaries/test_corpus/interprocedural_alias.zip') - - def test_binary___jump_loop(self): - self.run_binary_test('suite/binaries/test_corpus/jump_loop.zip') - - def test_binary___jumptable_aarch64(self): - self.run_binary_test('suite/binaries/test_corpus/jumptable_aarch64.zip') - - def test_binary___jumptable_mips32(self): - self.run_binary_test('suite/binaries/test_corpus/jumptable_mips32.zip') - - def test_binary___jumptable_multiple_indirect(self): - self.run_binary_test('suite/binaries/test_corpus/jumptable_multiple_indirect.zip') - - def test_binary___jumptable_no_range_check(self): - self.run_binary_test('suite/binaries/test_corpus/jumptable_no_range_check.zip') - - def test_binary___jumptable_reordered(self): - self.run_binary_test('suite/binaries/test_corpus/jumptable_reordered.zip') - - def test_binary___jumptable_x86(self): - self.run_binary_test('suite/binaries/test_corpus/jumptable_x86.zip') - - def test_binary___jumptable_x86_64(self): - self.run_binary_test('suite/binaries/test_corpus/jumptable_x86_64.zip') - - def test_binary___loop_constant_propagate(self): - self.run_binary_test('suite/binaries/test_corpus/loop_constant_propagate.zip') - - def test_binary___partial_register_dataflow(self): - self.run_binary_test('suite/binaries/test_corpus/partial_register_dataflow.zip') - - def test_binary___pe_thumb(self): - self.run_binary_test('suite/binaries/test_corpus/pe_thumb.zip') - - def test_binary___quick3dcoreplugin_dll(self): - self.run_binary_test('suite/binaries/test_corpus/quick3dcoreplugin.dll.zip') - - def test_binary___rangecheck(self): - self.run_binary_test('suite/binaries/test_corpus/rangecheck.zip') - - def test_binary___switch_linux_ppc_le_32(self): - self.run_binary_test('suite/binaries/test_corpus/switch_linux_ppc_le_32.zip') - - def test_binary___x87(self): - self.run_binary_test('suite/binaries/test_corpus/x87.zip') - - -if __name__ == "__main__": - - test_suite = unittest.defaultTestLoader.loadTestsFromModule(api_test) - test_suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestBinaryNinjaAPI)) - runner = unittest.TextTestRunner(verbosity=2) - runner.run(test_suite) |