| import lldb | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.lldbtest import * | |
| from lldbsuite.test import lldbutil | |
| from lldbgdbserverutils import get_debugserver_exe | |
| import os | |
| import platform | |
| import shutil | |
| import time | |
| import socket | |
| class TestStopAtEntry(TestBase): | |
| NO_DEBUG_INFO_TESTCASE = True | |
| # The port used by debugserver. | |
| PORT = 54638 | |
| # The number of attempts. | |
| ATTEMPTS = 10 | |
| # Time given to the binary to launch and to debugserver to attach to it for | |
| # every attempt. We'll wait a maximum of 10 times 2 seconds while the | |
| # inferior will wait 10 times 10 seconds. | |
| TIMEOUT = 2 | |
| def no_debugserver(self): | |
| if get_debugserver_exe() is None: | |
| return "no debugserver" | |
| return None | |
| def port_not_available(self): | |
| s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
| if s.connect_ex(("127.0.0.1", self.PORT)) == 0: | |
| return "{} not available".format(self.PORT) | |
| return None | |
| @skipUnlessDarwin | |
| @skipIfRemote | |
| def test_stop_default_platform_sync(self): | |
| self.do_test_stop_at_entry(True, False) | |
| @skipUnlessDarwin | |
| @skipIfRemote | |
| def test_stop_default_platform_async(self): | |
| self.do_test_stop_at_entry(False, False) | |
| @skipUnlessDarwin | |
| @skipIfRemote | |
| @skipTestIfFn(no_debugserver) | |
| @skipTestIfFn(port_not_available) | |
| def test_stop_remote_platform_sync(self): | |
| self.do_test_stop_at_entry(True, True) | |
| @skipUnlessDarwin | |
| @skipIfRemote | |
| @skipTestIfFn(no_debugserver) | |
| @skipTestIfFn(port_not_available) | |
| def test_stop_remote_platform_async(self): | |
| self.do_test_stop_at_entry(False, True) | |
| def do_test_stop_at_entry(self, synchronous, remote): | |
| """Test the normal launch path in either sync or async mode""" | |
| self.build() | |
| target = lldbutil.run_to_breakpoint_make_target(self) | |
| launch_info = target.GetLaunchInfo() | |
| launch_info.SetLaunchFlags(lldb.eLaunchFlagStopAtEntry) | |
| old_async = self.dbg.GetAsync() | |
| def cleanup(): | |
| self.dbg.SetAsync(old_async) | |
| self.addTearDownHook(cleanup) | |
| if not synchronous: | |
| self.dbg.SetAsync(True) | |
| listener = lldb.SBListener("test-process-listener") | |
| mask = listener.StartListeningForEventClass( | |
| self.dbg, | |
| lldb.SBProcess.GetBroadcasterClassName(), | |
| lldb.SBProcess.eBroadcastBitStateChanged, | |
| ) | |
| self.assertEqual( | |
| mask, | |
| lldb.SBProcess.eBroadcastBitStateChanged, | |
| "Got right mask for listener", | |
| ) | |
| launch_info.SetListener(listener) | |
| else: | |
| self.dbg.SetAsync(False) | |
| if remote: | |
| self.setup_remote_platform() | |
| error = lldb.SBError() | |
| process = target.Launch(launch_info, error) | |
| self.assertSuccess(error, "Launch failed") | |
| # If we are asynchronous, we have to wait for the events: | |
| if not synchronous: | |
| listener = launch_info.GetListener() | |
| event = lldb.SBEvent() | |
| result = listener.WaitForEvent(30, event) | |
| self.assertTrue(result, "Timed out waiting for event from process") | |
| state = lldb.SBProcess.GetStateFromEvent(event) | |
| self.assertState( | |
| state, lldb.eStateStopped, "Didn't get a stopped state after launch" | |
| ) | |
| # Okay, we should be stopped. Make sure we are indeed at the | |
| # entry point. I only know how to do this on darwin: | |
| self.assertEqual( | |
| len(process.threads), 1, "Should only have one thread at entry" | |
| ) | |
| thread = process.threads[0] | |
| frame = thread.GetFrameAtIndex(0) | |
| stop_func = frame.name | |
| self.assertEqual(stop_func, "_dyld_start") | |
| # Now make sure that we can resume the process and have it exit. | |
| error = process.Continue() | |
| self.assertSuccess(error, "Error continuing") | |
| # Fetch events till we get eStateExited: | |
| if not synchronous: | |
| # Get events till exited. | |
| listener = launch_info.GetListener() | |
| event = lldb.SBEvent() | |
| # We get two running events in a row here??? That's a bug | |
| # but not the one I'm testing for, so for now just fetch as | |
| # many as were sent. | |
| num_running = 0 | |
| state = lldb.eStateRunning | |
| while state == lldb.eStateRunning: | |
| num_running += 1 | |
| result = listener.WaitForEvent(30, event) | |
| self.assertTrue(result, "Timed out waiting for running") | |
| state = lldb.SBProcess.GetStateFromEvent(event) | |
| if num_running == 1: | |
| self.assertState(state, lldb.eStateRunning, "Got running event") | |
| # The last event we should get is the exited event | |
| self.assertState(state, lldb.eStateExited, "Got exit event") | |
| else: | |
| # Make sure that the process has indeed exited | |
| state = process.GetState() | |
| self.assertState(state, lldb.eStateExited) | |
| def setup_remote_platform(self): | |
| return | |
| self.build() | |
| exe = self.getBuildArtifact("a.out") | |
| # Launch our test binary. | |
| # Attach to it with debugserver. | |
| debugserver = get_debugserver_exe() | |
| debugserver_args = ["localhost:{}".format(self.PORT)] | |
| self.spawnSubprocess(debugserver, debugserver_args) | |
| # Select the platform. | |
| self.expect("platform select remote-macosx", substrs=[sdk_dir]) | |
| # Connect to debugserver | |
| interpreter = self.dbg.GetCommandInterpreter() | |
| connected = False | |
| for i in range(self.ATTEMPTS): | |
| result = lldb.SBCommandReturnObject() | |
| interpreter.HandleCommand("gdb-remote {}".format(self.PORT), result) | |
| connected = result.Succeeded() | |
| if connected: | |
| break | |
| time.sleep(self.TIMEOUT) | |
| self.assertTrue(connected, "could not connect to debugserver") |