blob: 3506c18688a9a839c5ccfff17af736bc64156017 [file] [edit]
"""
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 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"
)
# 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()