Skip to content

Commit 0dce3d5

Browse files
committed
[libunwind] Disable ZA before resuming from unwinding (on Linux)
This patch reimplements the SME ABI `__arm_za_disable` routine within libunwind. This routine must be called before resuming from unwinding on AArch64 platforms with SME support. Before calling the routine, we need to check that SME is available. In this patch, this is implemented for Linux-based platforms by checking HWCAP2. It should be possible to implement this check for other platforms as required. This patch includes a test for this functionality. This test requires SME, so on platforms without it, it will simply pass.
1 parent 4678f16 commit 0dce3d5

File tree

4 files changed

+203
-16
lines changed

4 files changed

+203
-16
lines changed

libunwind/src/Registers.hpp

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
#include "libunwind_ext.h"
2121
#include "shadow_stack_unwind.h"
2222

23+
#if __has_include(<sys/auxv.h>)
24+
#include <sys/auxv.h>
25+
#define HAVE_SYS_AUXV_H
26+
#endif
27+
2328
namespace libunwind {
2429

2530
// For emulating 128-bit registers
@@ -1828,6 +1833,7 @@ inline const char *Registers_ppc64::getRegisterName(int regNum) {
18281833
/// process.
18291834
class _LIBUNWIND_HIDDEN Registers_arm64;
18301835
extern "C" void __libunwind_Registers_arm64_jumpto(Registers_arm64 *);
1836+
extern "C" bool __libunwind_Registers_arm64_za_disable();
18311837

18321838
#if defined(_LIBUNWIND_USE_GCS)
18331839
extern "C" void *__libunwind_shstk_get_jump_target() {
@@ -1837,7 +1843,7 @@ extern "C" void *__libunwind_shstk_get_jump_target() {
18371843

18381844
class _LIBUNWIND_HIDDEN Registers_arm64 {
18391845
public:
1840-
Registers_arm64();
1846+
Registers_arm64() = default;
18411847
Registers_arm64(const void *registers);
18421848
Registers_arm64(const Registers_arm64 &);
18431849
Registers_arm64 &operator=(const Registers_arm64 &);
@@ -1855,7 +1861,10 @@ class _LIBUNWIND_HIDDEN Registers_arm64 {
18551861
v128 getVectorRegister(int num) const;
18561862
void setVectorRegister(int num, v128 value);
18571863
static const char *getRegisterName(int num);
1858-
void jumpto() { __libunwind_Registers_arm64_jumpto(this); }
1864+
void jumpto() {
1865+
zaDisable();
1866+
__libunwind_Registers_arm64_jumpto(this);
1867+
}
18591868
static constexpr int lastDwarfRegNum() {
18601869
return _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM64;
18611870
}
@@ -1908,25 +1917,43 @@ class _LIBUNWIND_HIDDEN Registers_arm64 {
19081917
private:
19091918
uint64_t lazyGetVG() const;
19101919

1920+
void zaDisable() const {
1921+
if (!_misc_registers.__has_sme)
1922+
return;
1923+
if (!__libunwind_Registers_arm64_za_disable())
1924+
_LIBUNWIND_ABORT("SME ZA disable failed");
1925+
}
1926+
1927+
static bool checkHasSME() {
1928+
#if defined(HAVE_SYS_AUXV_H)
1929+
constexpr int hwcap2_sme = (1 << 23);
1930+
unsigned long hwcap2 = getauxval(AT_HWCAP2);
1931+
return (hwcap2 & hwcap2_sme) != 0;
1932+
#endif
1933+
// TODO: Support other platforms.
1934+
return false;
1935+
}
1936+
19111937
struct GPRs {
1912-
uint64_t __x[29]; // x0-x28
1913-
uint64_t __fp; // Frame pointer x29
1914-
uint64_t __lr; // Link register x30
1915-
uint64_t __sp; // Stack pointer x31
1916-
uint64_t __pc; // Program counter
1917-
uint64_t __ra_sign_state; // RA sign state register
1938+
uint64_t __x[29] = {}; // x0-x28
1939+
uint64_t __fp = 0; // Frame pointer x29
1940+
uint64_t __lr = 0; // Link register x30
1941+
uint64_t __sp = 0; // Stack pointer x31
1942+
uint64_t __pc = 0; // Program counter
1943+
uint64_t __ra_sign_state = 0; // RA sign state register
19181944
};
19191945

19201946
struct Misc {
1921-
mutable uint64_t __vg = 0; // Vector Granule
1947+
mutable uint32_t __vg = 0; // Vector Granule
1948+
bool __has_sme = checkHasSME();
19221949
};
19231950

1924-
GPRs _registers;
1951+
GPRs _registers = {};
19251952
// Currently only the lower double in 128-bit vectore registers
19261953
// is perserved during unwinding. We could define new register
19271954
// numbers (> 96) which mean whole vector registers, then this
19281955
// struct would need to change to contain whole vector registers.
1929-
double _vectorHalfRegisters[32];
1956+
double _vectorHalfRegisters[32] = {};
19301957

19311958
// Miscellaneous/virtual registers. These are stored below the GPRs and FPRs
19321959
// as they do not correspond to physical registers, so do not need to be
@@ -1971,10 +1998,6 @@ Registers_arm64::operator=(const Registers_arm64 &other) {
19711998
return *this;
19721999
}
19732000

1974-
inline Registers_arm64::Registers_arm64() {
1975-
memset(static_cast<void *>(this), 0, sizeof(*this));
1976-
}
1977-
19782001
inline bool Registers_arm64::validRegister(int regNum) const {
19792002
if (regNum == UNW_REG_IP)
19802003
return true;

libunwind/src/UnwindRegistersSave.S

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,54 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext)
827827
ret
828828
#endif
829829

830+
//
831+
// extern "C" bool __libunwind_Registers_arm64_za_disable()
832+
//
833+
// On return:
834+
// success (true/false) is returned in x0
835+
//
836+
.p2align 2
837+
DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_arm64_za_disable)
838+
// If TPIDR2_EL0 is null, the subroutine just disables ZA.
839+
.inst 0xd53bd0b0 // mrs x16, TPIDR2_EL0
840+
cbz x16, 1f
841+
842+
// If any of the reserved bytes in the first 16 bytes of the TPIDR2 block are
843+
// nonzero, return false (libunwind will then abort).
844+
ldrh w14, [x16, #10]
845+
cbnz w14, 2f
846+
ldr w14, [x16, #12]
847+
cbnz w14, 2f
848+
849+
// If num_za_save_slices is zero, the subroutine just disables ZA.
850+
ldrh w14, [x16, #8]
851+
cbz x14, 1f
852+
853+
// If za_save_buffer is NULL, the subroutine just disables ZA.
854+
ldr x16, [x16]
855+
cbz x16, 1f
856+
857+
// Store ZA to za_save_buffer.
858+
mov x15, xzr
859+
0:
860+
.inst 0xe1206200 // str za[w15,0], [x16]
861+
.inst 0x04305830 // addsvl x16, x16, #1
862+
add x15, x15, #1
863+
cmp x14, x15
864+
b.ne 0b
865+
1:
866+
// * Set TPIDR2_EL0 to null.
867+
.inst 0xd51bd0bf // msr TPIDR2_EL0, xzr
868+
// * Set PSTATE.ZA to 0.
869+
.inst 0xd503447f // smstop za
870+
// * Return true (success)
871+
mov x0, #1
872+
ret
873+
2:
874+
// * Return false (failure/invalid TPIDR2 block)
875+
mov x0, #0
876+
ret
877+
830878
#elif defined(__arm__) && !defined(__APPLE__)
831879

832880
#if !defined(__ARM_ARCH_ISA_ARM)

libunwind/src/libunwind.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,4 +514,3 @@ bool logDWARF() {
514514
}
515515

516516
#endif // NDEBUG
517-
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// REQUIRES: linux && target={{aarch64-.+}}
10+
11+
#include <libunwind.h>
12+
#include <stdint.h>
13+
#include <stdio.h>
14+
#include <stdlib.h>
15+
#include <string.h>
16+
#include <sys/auxv.h>
17+
18+
// Basic test of unwinding with SME lazy saves. This tests libunwind disables ZA
19+
// (and commits a lazy save of ZA) before resuming from unwinding.
20+
21+
// Note: This test requires SME (and is setup to pass on targets without SME).
22+
23+
static bool checkHasSME() {
24+
constexpr int hwcap2_sme = (1 << 23);
25+
unsigned long hwcap2 = getauxval(AT_HWCAP2);
26+
return (hwcap2 & hwcap2_sme) != 0;
27+
}
28+
29+
struct TPIDR2Block {
30+
void *za_save_buffer;
31+
uint64_t num_save_slices;
32+
};
33+
34+
__attribute__((noinline)) void private_za() {
35+
// Note: Lazy save active on entry to function.
36+
unw_context_t context;
37+
unw_cursor_t cursor;
38+
39+
unw_getcontext(&context);
40+
unw_init_local(&cursor, &context);
41+
unw_step(&cursor);
42+
unw_resume(&cursor);
43+
}
44+
45+
bool isZAOn() {
46+
register uint64_t svcr asm("x20");
47+
asm(".inst 0xd53b4254" : "=r"(svcr));
48+
return (svcr & 0b10) != 0;
49+
}
50+
51+
__attribute__((noinline)) void za_function_with_lazy_save() {
52+
register uint64_t tmp asm("x8");
53+
54+
// SMSTART ZA (should zero ZA)
55+
asm(".inst 0xd503457f");
56+
57+
// RDSVL x8, #1 (read streaming vector length)
58+
asm(".inst 0x04bf5828" : "=r"(tmp));
59+
60+
// Allocate and fill ZA save buffer with 0xAA.
61+
size_t buffer_size = tmp * tmp;
62+
uint8_t *za_save_buffer = (uint8_t *)alloca(buffer_size);
63+
memset(za_save_buffer, 0xAA, buffer_size);
64+
65+
TPIDR2Block block = {za_save_buffer, tmp};
66+
tmp = reinterpret_cast<uint64_t>(&block);
67+
68+
// MRS TPIDR2_EL0, x8 (setup lazy save of ZA)
69+
asm(".inst 0xd51bd0a8" ::"r"(tmp));
70+
71+
// ZA should be on before unwinding.
72+
if (!isZAOn()) {
73+
fprintf(stderr, __FILE__ ": fail (ZA not on before call)\n");
74+
abort();
75+
} else {
76+
fprintf(stderr, __FILE__ ": pass (ZA on before call)\n");
77+
}
78+
79+
private_za();
80+
81+
// ZA should be off after unwinding.
82+
if (isZAOn()) {
83+
fprintf(stderr, __FILE__ ": fail (ZA on after unwinding)\n");
84+
abort();
85+
} else {
86+
fprintf(stderr, __FILE__ ": pass (ZA off after unwinding)\n");
87+
}
88+
89+
// MRS x8, TPIDR2_EL0 (read TPIDR2_EL0)
90+
asm(".inst 0xd53bd0a8" : "=r"(tmp));
91+
// ZA should have been saved (TPIDR2_EL0 zero).
92+
if (tmp != 0) {
93+
fprintf(stderr, __FILE__ ": fail (TPIDR2_EL0 non-null after unwinding)\n");
94+
abort();
95+
} else {
96+
fprintf(stderr, __FILE__ ": pass (TPIDR2_EL0 null after unwinding)\n");
97+
}
98+
99+
// ZA (all zero) should have been saved to the buffer.
100+
for (unsigned i = 0; i < buffer_size; ++i) {
101+
if (za_save_buffer[i] != 0) {
102+
fprintf(stderr,
103+
__FILE__ ": fail (za_save_buffer non-zero after unwinding)\n");
104+
abort();
105+
}
106+
}
107+
fprintf(stderr, __FILE__ ": pass (za_save_buffer zero'd after unwinding)\n");
108+
}
109+
110+
int main(int, char **) {
111+
if (!checkHasSME()) {
112+
fprintf(stderr, __FILE__ ": pass (no SME support)\n");
113+
return 0; // Pass (SME is required for this test to run).
114+
}
115+
za_function_with_lazy_save();
116+
return 0;
117+
}

0 commit comments

Comments
 (0)