| import lldb | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.lldbtest import * | |
| from lldbsuite.test import lldbutil | |
| from lldbsuite.test.lldbarm64e import Arm64eTestBase | |
| class TestPtrAuthExpressions(Arm64eTestBase): | |
| NO_DEBUG_INFO_TESTCASE = True | |
| SHARED_BUILD_TESTCASE = False | |
| 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", | |
| ) | |
| 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", | |
| ) | |
| 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", | |
| ) | |
| 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", | |
| ) |