[lldb][AArch64][Linux] Add Floating Point Mode Register (#106695)

Otherwise known as FEAT_FPMR. This register controls the behaviour of
floating point operations.

https://developer.arm.com/documentation/ddi0601/2024-06/AArch64-Registers/FPMR--Floating-point-Mode-Register

As the current floating point register contexts are fixed size, this has
been placed in a new set. Linux kernel patches have landed already, so
you can cross check with those.

To simplify testing we're not going to do any floating point operations,
just read and write from the program and debugger to make sure each sees
the other's values correctly.
This commit is contained in:
David Spickett
2024-09-25 10:27:57 +01:00
committed by GitHub
parent 02f46d7fb8
commit 5ee2deac0c
10 changed files with 252 additions and 2 deletions

View File

@@ -0,0 +1,3 @@
C_SOURCES := main.c
include Makefile.rules

View File

@@ -0,0 +1,58 @@
"""
Test lldb's ability to read and write the AArch64 FPMR register.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class AArch64LinuxFPMR(TestBase):
NO_DEBUG_INFO_TESTCASE = True
@skipUnlessArch("aarch64")
@skipUnlessPlatform(["linux"])
def test_fpmr_register(self):
if not self.isAArch64FPMR():
self.skipTest("FPMR must be present.")
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"],
)
# This has been set by the program.
expected_fpmr = (0b101010 << 32) | 0b101
self.expect(
"register read --all",
substrs=["Floating Point Mode Register", f"fpmr = {expected_fpmr:#018x}"],
)
# Write a value for the program to find. Same fields but with bit values
# inverted.
new_fpmr = (0b010101 << 32) | 0b010
self.runCmd(f"register write fpmr {new_fpmr:#x}")
# This value should be saved and restored after expressions.
self.runCmd("p expr_func()")
self.expect("register read fpmr", substrs=[f"fpmr = {new_fpmr:#018x}"])
# 0 means the program found the new value in the sysreg as expected.
self.expect("continue", substrs=["exited with status = 0"])

View File

@@ -0,0 +1,41 @@
#include <asm/hwcap.h>
#include <stdint.h>
#include <sys/auxv.h>
#ifndef HWCAP2_FPMR
#define HWCAP2_FPMR (1UL << 48)
#endif
uint64_t get_fpmr(void) {
uint64_t fpmr = 0;
__asm__ volatile("mrs %0, s3_3_c4_c4_2" : "=r"(fpmr));
return fpmr;
}
void set_fpmr(uint64_t value) {
__asm__ volatile("msr s3_3_c4_c4_2, %0" ::"r"(value));
}
// Set F8S1 (bits 0-2) and LSCALE2 (bits 37-32) (to prove we treat fpmr as 64
// bit).
const uint64_t original_fpmr = (uint64_t)0b101010 << 32 | (uint64_t)0b101;
void expr_func() { set_fpmr(original_fpmr); }
int main(int argc, char *argv[]) {
if (!(getauxval(AT_HWCAP2) & HWCAP2_FPMR))
return 1;
// As FPMR controls a bunch of floating point options that are quite
// extensive, we're not going to run any floating point ops here. Instead just
// update the value from the debugger and check it from this program, and vice
// versa.
set_fpmr(original_fpmr);
// Here the debugger checks it read back the value above, then writes in a new
// value. Note that the bits are flipped in the new value.
uint64_t new_fpmr = get_fpmr(); // Set break point at this line.
uint64_t expected_fpmr = ((uint64_t)0b010101 << 32) | (uint64_t)0b010;
return new_fpmr == expected_fpmr ? 0 : 1;
}