diff --git a/sysdeps/aarch64/Makefile b/sysdeps/aarch64/Makefile index 94baaf5..3ec78fe 100644 --- a/sysdeps/aarch64/Makefile +++ b/sysdeps/aarch64/Makefile @@ -3,6 +3,13 @@ long-double-fcts = yes ifeq ($(subdir),elf) sysdep-dl-routines += tlsdesc dl-tlsdesc gen-as-const-headers += dl-link.sym + +ifeq (yes,$(aarch64-variant-pcs)) +tests += tst-vpcs +modules-names += tst-vpcs-mod +LDFLAGS-tst-vpcs-mod.so = -Wl,-z,lazy +$(objpfx)tst-vpcs: $(objpfx)tst-vpcs-mod.so +endif endif ifeq ($(subdir),csu) diff --git a/sysdeps/aarch64/configure b/sysdeps/aarch64/configure index 5bd355a..f78a793 100644 --- a/sysdeps/aarch64/configure +++ b/sysdeps/aarch64/configure @@ -172,3 +172,43 @@ else config_vars="$config_vars default-abi = lp64" fi + +# Check if binutils supports variant PCS symbols. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for variant PCS support" >&5 +$as_echo_n "checking for variant PCS support... " >&6; } +if ${libc_cv_aarch64_variant_pcs+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat > conftest.S <&5 + (eval $ac_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; } \ + && { ac_try='$READELF -dW conftest.so | grep -q AARCH64_VARIANT_PCS' + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5 + (eval $ac_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; } + then + libc_cv_aarch64_variant_pcs=yes + fi + rm -rf conftest.* +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $libc_cv_aarch64_variant_pcs" >&5 +$as_echo "$libc_cv_aarch64_variant_pcs" >&6; } +config_vars="$config_vars +aarch64-variant-pcs = $libc_cv_aarch64_variant_pcs" diff --git a/sysdeps/aarch64/configure.ac b/sysdeps/aarch64/configure.ac index 7851dd4..7f13bfb 100644 --- a/sysdeps/aarch64/configure.ac +++ b/sysdeps/aarch64/configure.ac @@ -20,3 +20,25 @@ if test $libc_cv_aarch64_be = yes; then else LIBC_CONFIG_VAR([default-abi], [lp64]) fi + +# Check if binutils supports variant PCS symbols. +AC_CACHE_CHECK([for variant PCS support], [libc_cv_aarch64_variant_pcs], [dnl + cat > conftest.S <. */ + + .variant_pcs vpcs_call + .global vpcs_call + .type vpcs_call, %function +vpcs_call: + .cfi_startproc + hint 34 /* bti c. */ + + /* Save register state to *x0. */ + stp x0, x1, [x0] + stp x2, x3, [x0, 16] + stp x4, x5, [x0, 32] + stp x6, x7, [x0, 48] + stp x8, x9, [x0, 64] + stp x10, x11, [x0, 80] + stp x12, x13, [x0, 96] + stp x14, x15, [x0, 112] + stp x16, x17, [x0, 128] + stp x18, x19, [x0, 144] + stp x20, x21, [x0, 160] + stp x22, x23, [x0, 176] + stp x24, x25, [x0, 192] + stp x26, x27, [x0, 208] + stp x28, x29, [x0, 224] + mov x1, sp + stp x30, x1, [x0, 240] + stp q0, q1, [x0, 256] + stp q2, q3, [x0, 288] + stp q4, q5, [x0, 320] + stp q6, q7, [x0, 352] + stp q8, q9, [x0, 384] + stp q10, q11, [x0, 416] + stp q12, q13, [x0, 448] + stp q14, q15, [x0, 480] + stp q16, q17, [x0, 512] + stp q18, q19, [x0, 544] + stp q20, q21, [x0, 576] + stp q22, q23, [x0, 608] + stp q24, q25, [x0, 640] + stp q26, q27, [x0, 672] + stp q28, q29, [x0, 704] + stp q30, q31, [x0, 736] + ret + .cfi_endproc + .size vpcs_call, .-vpcs_call + + .global vpcs_call_regs + .type vpcs_call_regs, %function +vpcs_call_regs: + .cfi_startproc + hint 34 /* bti c. */ + + stp x29, x30, [sp, -160]! + mov x29, sp + + /* Save callee-saved registers. */ + stp x19, x20, [sp, 16] + stp x21, x22, [sp, 32] + stp x23, x24, [sp, 48] + stp x25, x26, [sp, 64] + stp x27, x28, [sp, 80] + stp d8, d9, [sp, 96] + stp d10, d11, [sp, 112] + stp d12, d13, [sp, 128] + stp d14, d15, [sp, 144] + + /* Initialize most registers from *x1, and save x0, x1, x29, x30, + and sp (== x29), so *x1 contains the register state. */ + stp x0, x1, [x1] + str x29, [x1, 232] + ldp x2, x3, [x1, 16] + ldp x4, x5, [x1, 32] + ldp x6, x7, [x1, 48] + ldp x8, x9, [x1, 64] + ldp x10, x11, [x1, 80] + ldp x12, x13, [x1, 96] + ldp x14, x15, [x1, 112] + ldp x16, x17, [x1, 128] + ldp x18, x19, [x1, 144] + ldp x20, x21, [x1, 160] + ldp x22, x23, [x1, 176] + ldp x24, x25, [x1, 192] + ldp x26, x27, [x1, 208] + ldr x28, [x1, 224] + /* Skip x29, x30, sp. */ + ldp q0, q1, [x1, 256] + ldp q2, q3, [x1, 288] + ldp q4, q5, [x1, 320] + ldp q6, q7, [x1, 352] + ldp q8, q9, [x1, 384] + ldp q10, q11, [x1, 416] + ldp q12, q13, [x1, 448] + ldp q14, q15, [x1, 480] + ldp q16, q17, [x1, 512] + ldp q18, q19, [x1, 544] + ldp q20, q21, [x1, 576] + ldp q22, q23, [x1, 608] + ldp q24, q25, [x1, 640] + ldp q26, q27, [x1, 672] + ldp q28, q29, [x1, 704] + ldp q30, q31, [x1, 736] + + /* Emulate a BL using B, but save x30 before the branch. */ + adr x30, .L_return_addr + stp x30, x29, [x1, 240] + b vpcs_call +.L_return_addr: + + /* Restore callee-saved registers. */ + ldp x19, x20, [sp, 16] + ldp x21, x22, [sp, 32] + ldp x23, x24, [sp, 48] + ldp x25, x26, [sp, 64] + ldp x27, x28, [sp, 80] + ldp d8, d9, [sp, 96] + ldp d10, d11, [sp, 112] + ldp d12, d13, [sp, 128] + ldp d14, d15, [sp, 144] + + ldp x29, x30, [sp], 160 + ret + .cfi_endproc + .size vpcs_call_regs, .-vpcs_call_regs diff --git a/sysdeps/aarch64/tst-vpcs.c b/sysdeps/aarch64/tst-vpcs.c new file mode 100644 index 0000000..92a701e --- /dev/null +++ b/sysdeps/aarch64/tst-vpcs.c @@ -0,0 +1,78 @@ +/* Test that variant PCS calls don't clobber registers with lazy binding. + Copyright (C) 2020 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include + +struct regs +{ + uint64_t x[32]; + union { + long double q[32]; + uint64_t u[64]; + } v; +}; + +/* Gives the registers in the caller and callee around a variant PCS call. + Most registers are initialized from BEFORE in the caller so they can + have values that likely show clobbers. Register state extensions such + as SVE is not covered here, only the base registers. */ +void vpcs_call_regs (struct regs *after, struct regs *before); + +static int +do_test (void) +{ + struct regs before, after; + int err = 0; + + unsigned char *p = (unsigned char *)&before; + for (int i = 0; i < sizeof before; i++) + p[i] = i & 0xff; + + vpcs_call_regs (&after, &before); + + for (int i = 0; i < 32; i++) + if (before.x[i] != after.x[i]) + { + if (i == 16 || i == 17) + /* Variant PCS allows clobbering x16 and x17. */ + continue; + err++; + printf ("x%d: before: 0x%016llx after: 0x%016llx\n", + i, + (unsigned long long)before.x[i], + (unsigned long long)after.x[i]); + } + for (int i = 0; i < 64; i++) + if (before.v.u[i] != after.v.u[i]) + { + err++; + printf ("v%d: before: 0x%016llx %016llx after: 0x%016llx %016llx\n", + i/2, + (unsigned long long)before.v.u[2*(i/2)+1], + (unsigned long long)before.v.u[2*(i/2)], + (unsigned long long)after.v.u[2*(i/2)+1], + (unsigned long long)after.v.u[2*(i/2)]); + } + if (err) + FAIL_EXIT1 ("The variant PCS call clobbered %d registers.\n", err); + return 0; +} + +#include