llvm-project/lldb/test/API/commands/expression/ptrauth/TestPtrAuthExpressions.py
Jonas Devlieghere 2b78c71cb5
[lldb] Support -fptrauth-indirect-gotos in the expression evaluator (#187562)
When targeting arm64e, we enable `-fptrauth-indirect-gotos` by default,
which signs label addresses and authenticates indirect branches. Add
support (and a test) for this in the LLDB expression evaluator.
2026-03-20 16:58:30 -05:00

119 lines
4.0 KiB
Python

import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class TestPtrAuthExpressions(TestBase):
NO_DEBUG_INFO_TESTCASE = True
@skipUnlessArm64eSupported
def test_static_function_pointer(self):
"""On arm64e, function pointers are automatically signed (PAC).
Test that we can call a function through a static function pointer
from the expression evaluator, which requires "fixing up" the pointer
signing via the InjectPointerSigningFixups pass."""
self.build()
lldbutil.run_to_source_breakpoint(
self, "// break here", lldb.SBFileSpec("main.c", False)
)
self.expect_expr(
"static int (*fp)(int, int) = &add; fp(5, 6);",
result_type="int",
result_value="11",
)
self.expect_expr(
"static int (*fp)(int, int) = &mul; fp(4, 5);",
result_type="int",
result_value="20",
)
@skipUnlessArm64eSupported
def test_indirect_call_through_caller(self):
"""Test that a function pointer passed to a debuggee function is
correctly signed. The caller() function in the debuggee forces a
genuine indirect call, preventing the compiler from folding the
function pointer call into a direct call."""
self.build()
lldbutil.run_to_source_breakpoint(
self, "// break here", lldb.SBFileSpec("main.c", False)
)
self.expect_expr(
"caller(add, 2, 3);",
result_type="int",
result_value="5",
)
self.expect_expr(
"caller(mul, 3, 7);",
result_type="int",
result_value="21",
)
@skipUnlessArm64eSupported
def test_debuggee_signed_pointer(self):
"""Test that a signed function pointer stored in the debuggee's memory
can be read and called from a user expression. The global_fp variable
is signed with the IB key (__ptrauth(1, 0, 0)), which is
process-specific; this verifies that auth succeeds because expressions
execute in the debuggee's process, not the debugger's."""
self.build()
lldbutil.run_to_source_breakpoint(
self, "// break here", lldb.SBFileSpec("main.c", False)
)
self.expect_expr(
"global_fp(10, 20);",
result_type="int",
result_value="30",
)
@skipUnlessArm64eSupported
def test_indirect_goto(self):
"""Test that computed gotos (GCC labels-as-values) work in the
expression evaluator on arm64e, where -fptrauth-indirect-gotos signs
label addresses and the indirect branch authenticates them."""
self.build()
lldbutil.run_to_source_breakpoint(
self, "// break here", lldb.SBFileSpec("main.c", False)
)
# Call a debuggee function that uses a computed-goto dispatch table.
self.expect_expr(
"indirect_goto_dispatch(0)",
result_type="int",
result_value="10",
)
self.expect_expr(
"indirect_goto_dispatch(1)",
result_type="int",
result_value="20",
)
self.expect_expr(
"indirect_goto_dispatch(2)",
result_type="int",
result_value="30",
)
# Evaluate a computed goto directly in a user expression.
# Use individual variables (not an array) so that the label addresses
# are signed inline with pacia/braa instructions, avoiding @AUTH
# relocations in global constant tables that RuntimeDyld cannot handle.
self.expect_expr(
"({ int result; void *t0 = &&L0, *t1 = &&L1, *t2 = &&L2; "
"goto *t1; "
"L0: result = 100; goto Lend; "
"L1: result = 200; goto Lend; "
"L2: result = 300; goto Lend; "
"Lend: result; })",
result_type="int",
result_value="200",
)