blob: 160c04f38a0a1964f67166a5979009d0dbff7316 [file]
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
from lldbsuite.test import configuration
class TestPtrAuthExpressions(TestBase):
NO_DEBUG_INFO_TESTCASE = True
SHARED_BUILD_TESTCASE = False
def build_arm64e(self):
self.build(
dictionary={"TRIPLE": configuration.triple.replace("arm64", "arm64e")}
)
@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_arm64e()
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_arm64e()
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_arm64e()
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_arm64e()
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",
)