1""" 2Test Debugger APIs. 3""" 4 5import lldb 6 7from lldbsuite.test.decorators import * 8from lldbsuite.test.lldbtest import * 9from lldbsuite.test import lldbutil 10 11 12class DebuggerAPITestCase(TestBase): 13 NO_DEBUG_INFO_TESTCASE = True 14 15 def test_debugger_api_boundary_condition(self): 16 """Exercise SBDebugger APIs with boundary conditions.""" 17 self.dbg.HandleCommand(None) 18 self.dbg.SetDefaultArchitecture(None) 19 self.dbg.GetScriptingLanguage(None) 20 self.dbg.CreateTarget(None) 21 self.dbg.CreateTarget(None, None, None, True, lldb.SBError()) 22 self.dbg.CreateTargetWithFileAndTargetTriple(None, None) 23 self.dbg.CreateTargetWithFileAndArch(None, None) 24 self.dbg.FindTargetWithFileAndArch(None, None) 25 self.dbg.SetInternalVariable(None, None, None) 26 self.dbg.GetInternalVariableValue(None, None) 27 # FIXME (filcab): We must first allow for the swig bindings to know if 28 # a Python callback is set. (Check python-typemaps.swig) 29 # self.dbg.SetLoggingCallback(None) 30 self.dbg.SetPrompt(None) 31 self.dbg.SetCurrentPlatform(None) 32 self.dbg.SetCurrentPlatformSDKRoot(None) 33 34 fresh_dbg = lldb.SBDebugger() 35 self.assertEqual(len(fresh_dbg), 0) 36 37 def test_debugger_delete_invalid_target(self): 38 """SBDebugger.DeleteTarget() should not crash LLDB given and invalid target.""" 39 target = lldb.SBTarget() 40 self.assertFalse(target.IsValid()) 41 self.dbg.DeleteTarget(target) 42 43 def test_debugger_internal_variables(self): 44 """Ensure that SBDebugger reachs the same instance of properties 45 regardless CommandInterpreter's context initialization""" 46 self.build() 47 exe = self.getBuildArtifact("a.out") 48 49 # Create a target by the debugger. 50 target = self.dbg.CreateTarget(exe) 51 self.assertTrue(target, VALID_TARGET) 52 53 property_name = "target.process.memory-cache-line-size" 54 55 def get_cache_line_size(): 56 value_list = lldb.SBStringList() 57 value_list = self.dbg.GetInternalVariableValue( 58 property_name, self.dbg.GetInstanceName() 59 ) 60 61 self.assertEqual(value_list.GetSize(), 1) 62 try: 63 return int(value_list.GetStringAtIndex(0)) 64 except ValueError as error: 65 self.fail("Value is not a number: " + error) 66 67 # Get global property value while there are no processes. 68 global_cache_line_size = get_cache_line_size() 69 70 # Run a process via SB interface. CommandInterpreter's execution context 71 # remains empty. 72 error = lldb.SBError() 73 launch_info = lldb.SBLaunchInfo(None) 74 launch_info.SetLaunchFlags(lldb.eLaunchFlagStopAtEntry) 75 process = target.Launch(launch_info, error) 76 self.assertTrue(process, PROCESS_IS_VALID) 77 78 # This should change the value of a process's local property. 79 new_cache_line_size = global_cache_line_size + 512 80 error = self.dbg.SetInternalVariable( 81 property_name, str(new_cache_line_size), self.dbg.GetInstanceName() 82 ) 83 self.assertSuccess(error, property_name + " value was changed successfully") 84 85 # Check that it was set actually. 86 self.assertEqual(get_cache_line_size(), new_cache_line_size) 87 88 # Run any command to initialize CommandInterpreter's execution context. 89 self.runCmd("target list") 90 91 # Test the local property again, is it set to new_cache_line_size? 92 self.assertEqual(get_cache_line_size(), new_cache_line_size) 93 94 def test_CreateTarget_platform(self): 95 exe = self.getBuildArtifact("a.out") 96 self.yaml2obj("elf.yaml", exe) 97 error = lldb.SBError() 98 target1 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error) 99 self.assertSuccess(error) 100 platform1 = target1.GetPlatform() 101 platform1.SetWorkingDirectory("/foo/bar") 102 103 # Reuse a platform if it matches the currently selected one... 104 target2 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error) 105 self.assertSuccess(error) 106 platform2 = target2.GetPlatform() 107 self.assertTrue( 108 platform2.GetWorkingDirectory().endswith("bar"), 109 platform2.GetWorkingDirectory(), 110 ) 111 112 # ... but create a new one if it doesn't. 113 self.dbg.SetSelectedPlatform(lldb.SBPlatform("remote-windows")) 114 target3 = self.dbg.CreateTarget(exe, None, "remote-linux", False, error) 115 self.assertSuccess(error) 116 platform3 = target3.GetPlatform() 117 self.assertIsNone(platform3.GetWorkingDirectory()) 118 119 def test_CreateTarget_arch(self): 120 exe = self.getBuildArtifact("a.out") 121 if lldbplatformutil.getHostPlatform() == "linux": 122 self.yaml2obj("macho.yaml", exe) 123 arch = "x86_64-apple-macosx" 124 expected_platform = "remote-macosx" 125 else: 126 self.yaml2obj("elf.yaml", exe) 127 arch = "x86_64-pc-linux" 128 expected_platform = "remote-linux" 129 130 fbsd = lldb.SBPlatform("remote-freebsd") 131 self.dbg.SetSelectedPlatform(fbsd) 132 133 error = lldb.SBError() 134 target1 = self.dbg.CreateTarget(exe, arch, None, False, error) 135 self.assertSuccess(error) 136 platform1 = target1.GetPlatform() 137 self.assertEqual(platform1.GetName(), expected_platform) 138 platform1.SetWorkingDirectory("/foo/bar") 139 140 # Reuse a platform even if it is not currently selected. 141 self.dbg.SetSelectedPlatform(fbsd) 142 target2 = self.dbg.CreateTarget(exe, arch, None, False, error) 143 self.assertSuccess(error) 144 platform2 = target2.GetPlatform() 145 self.assertEqual(platform2.GetName(), expected_platform) 146 self.assertTrue( 147 platform2.GetWorkingDirectory().endswith("bar"), 148 platform2.GetWorkingDirectory(), 149 ) 150 151 def test_SetDestroyCallback(self): 152 destroy_dbg_id = None 153 154 def foo(dbg_id): 155 # Need nonlocal to modify closure variable. 156 nonlocal destroy_dbg_id 157 destroy_dbg_id = dbg_id 158 159 self.dbg.SetDestroyCallback(foo) 160 161 original_dbg_id = self.dbg.GetID() 162 self.dbg.Destroy(self.dbg) 163 self.assertEqual(destroy_dbg_id, original_dbg_id) 164 165 def test_AddDestroyCallback(self): 166 original_dbg_id = self.dbg.GetID() 167 called = [] 168 169 def foo(dbg_id): 170 # Need nonlocal to modify closure variable. 171 nonlocal called 172 called += [('foo', dbg_id)] 173 174 def bar(dbg_id): 175 # Need nonlocal to modify closure variable. 176 nonlocal called 177 called += [('bar', dbg_id)] 178 179 token_foo = self.dbg.AddDestroyCallback(foo) 180 token_bar = self.dbg.AddDestroyCallback(bar) 181 self.dbg.Destroy(self.dbg) 182 183 # Should call both `foo()` and `bar()`. 184 self.assertEqual(called, [ 185 ('foo', original_dbg_id), 186 ('bar', original_dbg_id), 187 ]) 188 189 def test_RemoveDestroyCallback(self): 190 original_dbg_id = self.dbg.GetID() 191 called = [] 192 193 def foo(dbg_id): 194 # Need nonlocal to modify closure variable. 195 nonlocal called 196 called += [('foo', dbg_id)] 197 198 def bar(dbg_id): 199 # Need nonlocal to modify closure variable. 200 nonlocal called 201 called += [('bar', dbg_id)] 202 203 token_foo = self.dbg.AddDestroyCallback(foo) 204 token_bar = self.dbg.AddDestroyCallback(bar) 205 ret = self.dbg.RemoveDestroyCallback(token_foo) 206 self.dbg.Destroy(self.dbg) 207 208 # `Remove` should be successful 209 self.assertTrue(ret) 210 # Should only call `bar()` 211 self.assertEqual(called, [('bar', original_dbg_id)]) 212 213 def test_RemoveDestroyCallback_invalid_token(self): 214 original_dbg_id = self.dbg.GetID() 215 magic_token_that_should_not_exist = 32413 216 called = [] 217 218 def foo(dbg_id): 219 # Need nonlocal to modify closure variable. 220 nonlocal called 221 called += [('foo', dbg_id)] 222 223 token_foo = self.dbg.AddDestroyCallback(foo) 224 ret = self.dbg.RemoveDestroyCallback(magic_token_that_should_not_exist) 225 self.dbg.Destroy(self.dbg) 226 227 # `Remove` should be unsuccessful 228 self.assertFalse(ret) 229 # Should call `foo()` 230 self.assertEqual(called, [('foo', original_dbg_id)]) 231 232 def test_HandleDestroyCallback(self): 233 """ 234 Validates: 235 1. AddDestroyCallback and RemoveDestroyCallback work during debugger destroy. 236 2. HandleDestroyCallback invokes all callbacks in FIFO order. 237 """ 238 original_dbg_id = self.dbg.GetID() 239 events = [] 240 bar_token = None 241 242 def foo(dbg_id): 243 # Need nonlocal to modify closure variable. 244 nonlocal events 245 events.append(('foo called', dbg_id)) 246 247 def bar(dbg_id): 248 # Need nonlocal to modify closure variable. 249 nonlocal events 250 events.append(('bar called', dbg_id)) 251 252 def add_foo(dbg_id): 253 # Need nonlocal to modify closure variable. 254 nonlocal events 255 events.append(('add_foo called', dbg_id)) 256 events.append(('foo token', self.dbg.AddDestroyCallback(foo))) 257 258 def remove_bar(dbg_id): 259 # Need nonlocal to modify closure variable. 260 nonlocal events 261 events.append(('remove_bar called', dbg_id)) 262 events.append(('remove bar ret', self.dbg.RemoveDestroyCallback(bar_token))) 263 264 # Setup 265 events.append(('add_foo token', self.dbg.AddDestroyCallback(add_foo))) 266 bar_token = self.dbg.AddDestroyCallback(bar) 267 events.append(('bar token', bar_token)) 268 events.append(('remove_bar token', self.dbg.AddDestroyCallback(remove_bar))) 269 # Destroy 270 self.dbg.Destroy(self.dbg) 271 272 self.assertEqual(events, [ 273 # Setup 274 ('add_foo token', 0), # add_foo should be added 275 ('bar token', 1), # bar should be added 276 ('remove_bar token', 2), # remove_bar should be added 277 # Destroy 278 ('add_foo called', original_dbg_id), # add_foo should be called 279 ('foo token', 3), # foo should be added 280 ('bar called', original_dbg_id), # bar should be called 281 ('remove_bar called', original_dbg_id), # remove_bar should be called 282 ('remove bar ret', False), # remove_bar should fail, because it's already invoked and removed 283 ('foo called', original_dbg_id), # foo should be called 284 ]) 285