diff --git a/gdb-port/tests/unit/ast.py b/gdb-port/tests/unit/ast.py index fe82ab4757f1253fabdea7e7b466316836bf39fd..49510934fe66ed7b0c63dba6569280ffefdf3f50 100644 --- a/gdb-port/tests/unit/ast.py +++ b/gdb-port/tests/unit/ast.py @@ -43,3 +43,36 @@ class HParseResultCreation(unittest.TestCase): gdbv_patcher.stop() self.assertIsNone(tok) + def test_read_member(self): + hpr_pointer_type_patcher = unittest.mock.patch.object(HParseResult, 'HParseResult_t_p', spec=gdb.Type) + gdbv_patcher = unittest.mock.patch('gdb.Value', autospec=True) + gdb_lookup_type_patcher = unittest.mock.patch.object(gdb, 'lookup_type') + hpr_ast_not_null_patcher = unittest.mock.patch.object(HParseResult, 'read_AST_not_null', return_value=True) + hpr_make_hparsedtoken_patcher = unittest.mock.patch.object(HParseResult, 'make_HParsedToken', spec=HParsedToken) + init_patcher = unittest.mock.patch.object(HParseResult, '__init__', return_value=None) + + hpr_pointer_type_patcher.start() + #hpr_ast_not_null_patcher.start() + #hpr_make_hparsedtoken_patcher.start() + gdbv_mock_object = gdbv_patcher.start() + gdb_lookup_type_mock_object = gdb_lookup_type_patcher.start() + init_patcher.start() + + result = HParseResult(0xdeadbeef) + result.address = 0xdeadbeef + member = result.read_member('ast') + + # Check that struct being indexed is at self.address + self.assertEqual(gdbv_mock_object.mock_calls[0], unittest.mock.call(0xdeadbeef)) + # Check that the value returned is parse_result_gdb_value['foo'] + self.assertEqual(gdbv_mock_object.mock_calls[-1], unittest.mock.call().cast().__getitem__('ast')) + # Check that gdb.lookup_type is not called + self.assertEqual(gdb_lookup_type_mock_object.mock_calls, []) + + def test_read_member_with_type_lookup(self): + gdb_lookup_type_patcher = unittest.mock.patch.object(gdb, 'lookup_type') + raise Exception("Not implemented") + + # TODO: should read_member employ a whitelist for member_name, or should this trigger an exeption? + def test_read_member_invalid_param(self): + raise Exception("Not implemented") diff --git a/gdb-port/tests/unit/breakpointmanager.py b/gdb-port/tests/unit/breakpointmanager.py new file mode 100644 index 0000000000000000000000000000000000000000..74941b0b4aa43796877902c61dad8fa5bc6108e6 --- /dev/null +++ b/gdb-port/tests/unit/breakpointmanager.py @@ -0,0 +1,169 @@ +import unittest +import unittest.mock + +class BreakpointManagerCreated(unittest.TestCase): + def setUp(self): + test_breakpoints = [ "init_runlengthdecode_parser", "init_LZW_parser" ] + self.bpm = BreakpointManager(test_breakpoints) + self.arch = gdb.selected_frame().architecture() + + def test_locate_retqs(self): + retqs = self.bpm.locate_retqs("init_runlengthdecode_parser") + addr = retqs[0] + instr = self.arch.disassemble(addr, addr+8, 1)[0] + self.assertTrue(instr['asm'].startswith("ret")) + + def test_locate_retq(self): + retq = self.bpm.locate_retq("init_runlengthdecode_parser") + instr = self.arch.disassemble(retq, retq+8, 1)[0] + self.assertTrue(instr['asm'].startswith("ret")) + + +class BreakpointManagerSettingBreakpoints(unittest.TestCase): + def setUp(self): + #TODO: make these tests independent of the pdf parser + test_breakpoints = [ "init_runlengthdecode_parser", "init_LZW_parser" ] + # "constants" like rld_retq below could be moved to setUpClass (possibly better performance) + self.bpm = BreakpointManager(test_breakpoints) + self.arch = gdb.selected_frame().architecture() + # TODO: instead of locate_retq, get bp address from call_list, and disassemble + self.rld_retq = self.bpm.locate_retq(test_breakpoints[0]) + self.lzw_retq = self.bpm.locate_retq(test_breakpoints[1]) + + self.hrbp_patcher = unittest.mock.patch('__main__.HRuleBreakpoint') + self.hrbp_mock_object = self.hrbp_patcher.start() + self.plprbp_patcher = unittest.mock.patch('__main__.PerformLowLevelParseRetBreakpoint') + self.plprbp_mock_object = self.plprbp_patcher.start() + self.hpprbp_patcher = unittest.mock.patch('__main__.HPackratParseRetBreakpoint') + self.hpprbp_mock_object = self.hpprbp_patcher.start() + self.pvbp_patcher = unittest.mock.patch('__main__.ParserVirtualBreakpoint') + self.pvbp_mock_object = self.pvbp_patcher.start() + self.ipbp_patcher = unittest.mock.patch('__main__.InitParserBreakpoint') + self.ipbp_mock_object = self.ipbp_patcher.start() + + self.hdpbp_patcher = unittest.mock.patch('__main__.HDoParseBreakpoint') + self.hdpbp_mock_object = self.hdpbp_patcher.start() + self.hppbp_patcher = unittest.mock.patch('__main__.HPackratParseBreakpoint') + self.hppbp_mock_object = self.hppbp_patcher.start() + self.pllpbp_patcher = unittest.mock.patch('__main__.PerformLowLevelParseBreakpoint') + self.pllpbp_mock_object = self.pllpbp_patcher.start() + self.hamrbp_patcher = unittest.mock.patch('__main__.HArenaMallocRawBreakpoint') + self.hamrbp_mock_object = self.hamrbp_patcher.start() + self.hdprbp_patcher = unittest.mock.patch('__main__.HDoParseRetBreakpoint') + self.hdprbp_mock_object = self.hdprbp_patcher.start() + + def tearDown(self): + self.hpprbp_patcher.stop() + self.plprbp_patcher.stop() + self.hrbp_patcher.stop() + self.pvbp_patcher.stop() + self.ipbp_patcher.stop() + self.hdpbp_patcher.stop() + self.hppbp_patcher.stop() + self.pllpbp_patcher.stop() + self.hamrbp_patcher.stop() + self.hdprbp_patcher.stop() + +# TODO: mock breakpoints, assert on arguments to constructor + def test_set_h_rule_breakpoints(self): + self.bpm.set_h_rule_breakpoints() + self.assertTrue(self.hrbp_mock_object.call_count == 2) + #bps_valid = [ bp.is_valid() for bp in self.bpm.h_rule_breakpoints] + # Assert that we have as many breakpoints as h_rule_function and is_valid() returns true for all of them + #self.assertEqual(bps_valid, len(self.bpm.H_RULE_FUNCTIONS) * [True]) + # Cleanup + #for bp in self.bpm.h_rule_breakpoints: + # bp.delete() + + def test_set_hammer_retq_breakpoints(self): + self.bpm.set_hammer_retq_breakpoints() + self.assertTrue(self.plprbp_mock_object.called) + self.assertTrue(self.hpprbp_mock_object.called) + self.assertTrue(self.hdprbp_mock_object.called) + #bps_valid = [ bp.is_valid() for bp in self.bpm.hammer_retq_breakpoints] + #self.assertEqual(bps_valid, 2 * [True]) + #Cleanup + #for bp in self.bpm.hammer_retq_breakpoints: + # bp.delete() + + def test_del_hammer_retq_breakpoints(self): + self.bpm.set_hammer_retq_breakpoints() + bps = self.bpm.hammer_retq_breakpoints + self.bpm.del_hammer_retq_breakpoints() + for bp in bps: + self.assertTrue(bp.delete.called) + #self.assertTrue(self.plprbp_mock_object.delete.called) + #self.assertTrue(self.hpprbp_mock_object.delete.called) + #bps_valid = [ bp.is_valid() for bp in bps] + #self.assertEqual(bps_valid, 2 * [False]) + + @unittest.skip("Need to make a mock binary to find RETQs in for this one") + def test_locate_retqs(self): + self.fail("Not implemented yet") + + @unittest.skip("Need to make a mock binary to find RETQs in for this one") + def test_locate_retq(self): + self.fail("Not implemented yet") + + def test_set_parser_virtual_breakpoints(self): + self.bpm.set_parser_virtual_breakpoints() + method_names = [ a for a in dir(self.bpm) if a.startswith("parse_")] + virt_bps = [ self.bpm.__getattribute__(a) for a in dir(self.bpm) if a.startswith("parse_")] + self.assertTrue(self.pvbp_mock_object.call_count == len(virt_bps)) + #print(self.pvbp_mock_object.mock_calls) + for i in range(0, len(method_names)): + with self.subTest(i=i): + self.pvbp_mock_object.assert_any_call(method_names[i]) + + def test_del_parser_virtual_breakpoints(self): + self.bpm.set_parser_virtual_breakpoints() + virt_bps = [ self.bpm.__getattribute__(a) for a in dir(self.bpm) if a.startswith("parse_")] + self.bpm.del_parser_virtual_breakpoints() + #TODO: in tests, all self.parse_* attributes refer to the same mock object + # so we can't just do virt_bps[i].delete.assert_called_once(), because it + # will have been called len(virt_bps) times. + # it would be desirable to have separate mock object instances, + # or for mock_object to know which attribute it has + # been called through, to allow checking individual self.parse_* + # breakpoints for deletion + self.assertTrue(len(virt_bps) > 0) + self.assertTrue(virt_bps[0].mock_calls == [unittest.mock.call.delete()] * len(virt_bps)) + #self.assertTrue(virt_bps[0].delete.call_count == len(virt_bps)) + #for i in range(0, len(virt_bps)): + # with self.subTest(i=i): + # virt_bps[i].delete.assert_called_once() + + def test_set_init_parser_breakpoint(self): + self.bpm.set_init_parser_breakpoint() + #print(self.ipbp_mock_object.call_args.args) + gdb_addr_expr = self.ipbp_mock_object.call_args.args[0] + bp_addr = int(gdb_addr_expr.strip('*'), 16) + #TODO: probably could just get the machine word size + instr = self.arch.disassemble(bp_addr, bp_addr+8, 1)[0] + self.assertTrue(instr['asm'].startswith('ret')) + + def test_del_init_parser_breakpoints(self): + self.bpm.set_init_parser_breakpoint() + self.bpm.del_init_parser_breakpoint() + self.assertTrue(self.bpm.init_parser_retq.mock_calls == [unittest.mock.call.delete()]) + + def test_set_hammer_breakpoints(self): + self.bpm.set_hammer_breakpoints() + bps = [self.hdpbp_mock_object, self.hppbp_mock_object, self.pllpbp_mock_object, self.hamrbp_mock_object] + for i in range(0, len(bps)): + with self.subTest(i=i): + self.assertTrue(bps[i].called) + + def test_del_hammer_breakpoints(self): + self.bpm.set_hammer_breakpoints() + bps = [self.bpm.h_do_parse, self.bpm.h_packrat_parse, self.bpm.perform_lowlevel_parse, self.bpm.h_arena_malloc_raw] + self.bpm.del_hammer_breakpoints() + for i in range(0, len(bps)): + with self.subTest(i=i): + self.assertTrue(bps[i].mock_calls == [unittest.mock.call.delete()]) + + # TODO + #def tearDown(self): + #self.bpm.delete + +#TODO: tests for PDFMainBreakpoint