| """ | |
| End-to-end test for accelerator plugin actions (breakpoints and connections). | |
| Launches a real process against an lldb-server that has the mock accelerator | |
| plugin enabled and verifies that the breakpoints requested by the plugin are | |
| set in the native process, hit, and that hitting one breakpoint can request | |
| further breakpoints. This exercises all three breakpoint types: by name, by | |
| name scoped to a shared library, and by address. | |
| It also verifies that hitting the plugin's connection-trigger breakpoint causes | |
| the client to create a second (accelerator) target and connect it to the mock | |
| accelerator GDB server. | |
| """ | |
| import os | |
| import lldb | |
| import lldbsuite.test.lldbutil as lldbutil | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.lldbtest import * | |
| from lldbsuite.test import configuration | |
| def uint64_to_int64(value): | |
| """Reinterpret an unsigned 64-bit value as a signed 64-bit integer.""" | |
| if value >= (1 << 63): | |
| return value - (1 << 64) | |
| return value | |
| class MockAcceleratorActionsTestCase(TestBase): | |
| NO_DEBUG_INFO_TESTCASE = True | |
| def setUp(self): | |
| super().setUp() | |
| if "mock-accelerator" not in configuration.enabled_plugins: | |
| self.skipTest("mock-accelerator plugin is not enabled") | |
| def check_accelerator_breakpoint_stop(self, process, function_name, hit_count=None): | |
| """Verify the process stopped at an internal accelerator breakpoint in | |
| the given function. If hit_count is not None, also verify the | |
| breakpoint's hit count. Returns the breakpoint.""" | |
| self.assertState(process.GetState(), lldb.eStateStopped) | |
| thread = process.GetSelectedThread() | |
| # The stop must be due to a breakpoint, and the frame must be in the | |
| # expected function. | |
| self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonBreakpoint) | |
| frame = thread.GetFrameAtIndex(0) | |
| self.assertEqual(frame.GetFunctionName(), function_name) | |
| # The breakpoint id is carried in the stop reason data. Accelerator | |
| # breakpoints are internal, so they are not in the public breakpoint | |
| # list, but can still be looked up by id. The datum is an unsigned | |
| # 64-bit value holding the (signed) breakpoint id; internal ids are | |
| # negative. | |
| self.assertGreater(thread.GetStopReasonDataCount(), 0) | |
| bp_id = uint64_to_int64(thread.GetStopReasonDataAtIndex(0)) | |
| bp = process.GetTarget().FindBreakpointByID(bp_id) | |
| self.assertTrue(bp.IsValid()) | |
| self.assertTrue(bp.IsInternal(), "accelerator breakpoints are internal") | |
| if hit_count is not None: | |
| self.assertEqual(bp.GetHitCount(), hit_count) | |
| return bp | |
| def set_mock_env(self, name, value): | |
| """Set an environment variable the mock plugin reads (it is inherited by | |
| the lldb-server that hosts the plugin), restoring it after the test.""" | |
| previous = os.environ.get(name) | |
| os.environ[name] = value | |
| def restore(): | |
| if previous is None: | |
| os.environ.pop(name, None) | |
| else: | |
| os.environ[name] = previous | |
| self.addTearDownHook(restore) | |
| @skipIfRemote | |
| @add_test_categories(["llgs"]) | |
| def test_accelerator_actions(self): | |
| """The mock accelerator plugin drives breakpoints in the inferior and, | |
| once initialized, a connection that creates a second target.""" | |
| self.build() | |
| exe = self.getBuildArtifact("a.out") | |
| target = self.dbg.CreateTarget(exe) | |
| self.assertTrue(target, VALID_TARGET) | |
| # Launching the process should stop at the | |
| # "mock_gpu_accelerator_initialize" breakpoint that the mock plugin | |
| # requested via jAcceleratorPluginInitialize (it requests the native | |
| # process not auto-resume). This is a breakpoint by name with no shared | |
| # library. | |
| process = target.LaunchSimple(None, None, self.get_process_working_directory()) | |
| self.assertTrue(process, PROCESS_IS_VALID) | |
| self.check_accelerator_breakpoint_stop( | |
| process, "mock_gpu_accelerator_initialize", hit_count=1 | |
| ) | |
| # The accelerator breakpoint was set and hit, yet it is internal, so it | |
| # never appears in the public breakpoint list. | |
| self.assertEqual(target.GetNumBreakpoints(), 0) | |
| # Hitting the mock_gpu_accelerator_initialize breakpoint caused the | |
| # plugin to request three more breakpoints: the connection hook (hit | |
| # next, since main() connects right after initializing), one by address | |
| # (on "mock_gpu_accelerator_compute", from the symbol value delivered | |
| # with the hit), and one by name scoped to the "a.out" shared library (on | |
| # "mock_gpu_accelerator_finish"). Only the native target exists until the | |
| # connection hook is hit. | |
| self.assertEqual(self.dbg.GetNumTargets(), 1) | |
| process.Continue() | |
| self.check_accelerator_breakpoint_stop( | |
| process, "mock_gpu_accelerator_connect", hit_count=1 | |
| ) | |
| # The accelerator target now exists alongside the native target. | |
| self.assertEqual(self.dbg.GetNumTargets(), 2) | |
| accelerator_target = None | |
| for i in range(self.dbg.GetNumTargets()): | |
| candidate = self.dbg.GetTargetAtIndex(i) | |
| if candidate != target: | |
| accelerator_target = candidate | |
| break | |
| self.assertTrue(accelerator_target.IsValid()) | |
| # The native and accelerator targets are related through one target | |
| # group and carry their respective roles. | |
| self.expect( | |
| "target groups list", | |
| substrs=[ | |
| "group #0: CPU and accelerator targets", | |
| "target #0: CPU", | |
| "target #1: accelerator", | |
| ], | |
| ) | |
| # The accelerator process must be successfully connected and stopped. | |
| accelerator_process = accelerator_target.GetProcess() | |
| self.assertTrue(accelerator_process.IsValid()) | |
| self.assertState(accelerator_process.GetState(), lldb.eStateStopped) | |
| # Validate the registers (each value == 0x1000 + register index). | |
| accelerator_frame = accelerator_process.GetThreadAtIndex(0).GetFrameAtIndex(0) | |
| expected_registers = { | |
| "r0": 0x1000, | |
| "r1": 0x1001, | |
| "sp": 0x1002, | |
| "fp": 0x1003, | |
| "pc": 0x1004, | |
| "flags": 0x1005, | |
| } | |
| for name, value in expected_registers.items(): | |
| reg = accelerator_frame.FindRegister(name) | |
| self.assertTrue(reg.IsValid(), "register %s should exist" % name) | |
| self.assertEqual( | |
| reg.GetValueAsUnsigned(), | |
| value, | |
| "register %s should read back its expected value" % name, | |
| ) | |
| # With the accelerator connected, continue through the remaining | |
| # breakpoint types: by address (on mock_gpu_accelerator_compute), then by | |
| # name scoped to a shared library (on mock_gpu_accelerator_finish). | |
| process.Continue() | |
| self.check_accelerator_breakpoint_stop( | |
| process, "mock_gpu_accelerator_compute", hit_count=1 | |
| ) | |
| process.Continue() | |
| self.check_accelerator_breakpoint_stop( | |
| process, "mock_gpu_accelerator_finish", hit_count=1 | |
| ) | |
| # No more accelerator breakpoints; the process runs to exit. | |
| process.Continue() | |
| self.assertState(process.GetState(), lldb.eStateExited) | |
| self.assertEqual(process.GetExitStatus(), 0) | |
| def run_expecting_failed_connection(self): | |
| """Run the inferior through its breakpoints, asserting the connection is | |
| attempted at the connection hook but no accelerator target is created | |
| (and lldb does not crash), then runs to exit.""" | |
| self.build() | |
| exe = self.getBuildArtifact("a.out") | |
| target = self.dbg.CreateTarget(exe) | |
| self.assertTrue(target, VALID_TARGET) | |
| process = target.LaunchSimple(None, None, self.get_process_working_directory()) | |
| self.assertTrue(process, PROCESS_IS_VALID) | |
| self.check_accelerator_breakpoint_stop( | |
| process, "mock_gpu_accelerator_initialize", hit_count=1 | |
| ) | |
| # At the connection hook the plugin returns connect_info the client | |
| # cannot use, so no second target is created and lldb does not crash. | |
| process.Continue() | |
| self.check_accelerator_breakpoint_stop( | |
| process, "mock_gpu_accelerator_connect", hit_count=1 | |
| ) | |
| self.assertEqual( | |
| self.dbg.GetNumTargets(), 1, "connection should fail; no accelerator target" | |
| ) | |
| self.expect("target groups list", substrs=["No target groups."]) | |
| # The native process is unaffected: the remaining breakpoints still fire | |
| # and it runs to exit. | |
| process.Continue() | |
| self.check_accelerator_breakpoint_stop( | |
| process, "mock_gpu_accelerator_compute", hit_count=1 | |
| ) | |
| process.Continue() | |
| self.check_accelerator_breakpoint_stop( | |
| process, "mock_gpu_accelerator_finish", hit_count=1 | |
| ) | |
| process.Continue() | |
| self.assertState(process.GetState(), lldb.eStateExited) | |
| self.assertEqual(process.GetExitStatus(), 0) | |
| @skipIfRemote | |
| @add_test_categories(["llgs"]) | |
| def test_accelerator_connection_invalid_platform(self): | |
| """An invalid platform name in connect_info fails the connection | |
| gracefully.""" | |
| self.set_mock_env("LLDB_MOCK_ACCELERATOR_PLATFORM", "no-such-platform") | |
| self.run_expecting_failed_connection() | |
| @skipIfRemote | |
| @add_test_categories(["llgs"]) | |
| def test_accelerator_connection_incompatible_triple(self): | |
| """A valid platform with a triple it does not support fails the | |
| connection gracefully.""" | |
| # remote-linux is a real platform but cannot handle a GPU triple. | |
| self.set_mock_env("LLDB_MOCK_ACCELERATOR_PLATFORM", "remote-linux") | |
| self.set_mock_env("LLDB_MOCK_ACCELERATOR_TRIPLE", "amdgcn-amd-amdhsa") | |
| self.run_expecting_failed_connection() | |
| @skipIfRemote | |
| @add_test_categories(["llgs"]) | |
| def test_accelerator_connection_failure_removes_target_group(self): | |
| """A connection failure after target creation removes the target group.""" | |
| self.set_mock_env("LLDB_MOCK_ACCELERATOR_CONNECT_URL", "connect://localhost:0") | |
| self.run_expecting_failed_connection() |