llvm-project/lldb/test/API/linux/aarch64/gcs/TestAArch64LinuxGCS.py
David Spickett 5658bc4ae7
[lldb][Linux] Add Control Protection Fault signal (#122917)
This will be sent by Arm's Guarded Control Stack extension when an
invalid return is executed.

The signal does have an address we could show, but it's the PC at which
the fault occured. The debugger has plenty of ways to show you that
already, so I've left it out.

```
(lldb) c
Process 460 resuming
Process 460 stopped
* thread #1, name = 'test', stop reason = signal SIGSEGV: control protection fault
    frame #0: 0x0000000000400784 test`main at main.c:57:1
   54  	  afunc();
   55  	  printf("return from main\n");
   56  	  return 0;
-> 57  	}
(lldb) dis
<...>
->  0x400784 <+100>: ret
```

The new test case generates the signal by corrupting the link register
then attempting to return. This will work whether we manually enable GCS
or the C library does it for us.

(in the former case you could just return from main and it would fault)
2025-01-21 09:24:41 +00:00

86 lines
2.7 KiB
Python

"""
Check that lldb features work when the AArch64 Guarded Control Stack (GCS)
extension is enabled.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class AArch64LinuxGCSTestCase(TestBase):
NO_DEBUG_INFO_TESTCASE = True
@skipUnlessArch("aarch64")
@skipUnlessPlatform(["linux"])
def test_gcs_region(self):
if not self.isAArch64GCS():
self.skipTest("Target must support GCS.")
# This test assumes that we have /proc/<PID>/smaps files
# that include "VmFlags:" lines.
# AArch64 kernel config defaults to enabling smaps with
# PROC_PAGE_MONITOR and "VmFlags" was added in kernel 3.8,
# before GCS was supported at all.
self.build()
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
lldbutil.run_break_set_by_file_and_line(
self,
"main.c",
line_number("main.c", "// Set break point at this line."),
num_expected_locations=1,
)
self.runCmd("run", RUN_SUCCEEDED)
if self.process().GetState() == lldb.eStateExited:
self.fail("Test program failed to run.")
self.expect(
"thread list",
STOPPED_DUE_TO_BREAKPOINT,
substrs=["stopped", "stop reason = breakpoint"],
)
# By now either the program or the system C library enabled GCS and there
# should be one region marked for use by it (we cannot predict exactly
# where it will be).
self.runCmd("memory region --all")
found_ss = False
for line in self.res.GetOutput().splitlines():
if line.strip() == "shadow stack: yes":
if found_ss:
self.fail("Found more than one shadow stack region.")
found_ss = True
self.assertTrue(found_ss, "Failed to find a shadow stack region.")
# Note that we must let the debugee get killed here as it cannot exit
# cleanly if GCS was manually enabled.
@skipUnlessArch("aarch64")
@skipUnlessPlatform(["linux"])
def test_gcs_fault(self):
if not self.isAArch64GCS():
self.skipTest("Target must support GCS.")
self.build()
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
self.runCmd("run", RUN_SUCCEEDED)
if self.process().GetState() == lldb.eStateExited:
self.fail("Test program failed to run.")
self.expect(
"thread list",
"Expected stopped by SIGSEGV.",
substrs=[
"stopped",
"stop reason = signal SIGSEGV: control protection fault",
],
)