| // Program that loops for ever doing lots of recursions and system calls, |
| // intended to be used as part of a stress test for GCS context switching. |
| // |
| // Copyright 2015-2023 Arm Ltd |
| |
| #include <asm/unistd.h> |
| |
| #define sa_sz 32 |
| #define sa_flags 8 |
| #define sa_handler 0 |
| #define sa_mask_sz 8 |
| |
| #define si_code 8 |
| |
| #define SIGINT 2 |
| #define SIGABRT 6 |
| #define SIGUSR1 10 |
| #define SIGSEGV 11 |
| #define SIGUSR2 12 |
| #define SIGTERM 15 |
| #define SEGV_CPERR 10 |
| |
| #define SA_NODEFER 1073741824 |
| #define SA_SIGINFO 4 |
| #define ucontext_regs 184 |
| |
| #define PR_SET_SHADOW_STACK_STATUS 75 |
| # define PR_SHADOW_STACK_ENABLE (1UL << 0) |
| |
| #define GCSPR_EL0 S3_3_C2_C5_1 |
| |
| .macro function name |
| .macro endfunction |
| .type \name, @function |
| .purgem endfunction |
| .endm |
| \name: |
| .endm |
| |
| // Print a single character x0 to stdout |
| // Clobbers x0-x2,x8 |
| function putc |
| str x0, [sp, #-16]! |
| |
| mov x0, #1 // STDOUT_FILENO |
| mov x1, sp |
| mov x2, #1 |
| mov x8, #__NR_write |
| svc #0 |
| |
| add sp, sp, #16 |
| ret |
| endfunction |
| .globl putc |
| |
| // Print a NUL-terminated string starting at address x0 to stdout |
| // Clobbers x0-x3,x8 |
| function puts |
| mov x1, x0 |
| |
| mov x2, #0 |
| 0: ldrb w3, [x0], #1 |
| cbz w3, 1f |
| add x2, x2, #1 |
| b 0b |
| |
| 1: mov w0, #1 // STDOUT_FILENO |
| mov x8, #__NR_write |
| svc #0 |
| |
| ret |
| endfunction |
| .globl puts |
| |
| // Utility macro to print a literal string |
| // Clobbers x0-x4,x8 |
| .macro puts string |
| .pushsection .rodata.str1.1, "aMS", @progbits, 1 |
| .L__puts_literal\@: .string "\string" |
| .popsection |
| |
| ldr x0, =.L__puts_literal\@ |
| bl puts |
| .endm |
| |
| // Print an unsigned decimal number x0 to stdout |
| // Clobbers x0-x4,x8 |
| function putdec |
| mov x1, sp |
| str x30, [sp, #-32]! // Result can't be > 20 digits |
| |
| mov x2, #0 |
| strb w2, [x1, #-1]! // Write the NUL terminator |
| |
| mov x2, #10 |
| 0: udiv x3, x0, x2 // div-mod loop to generate the digits |
| msub x0, x3, x2, x0 |
| add w0, w0, #'0' |
| strb w0, [x1, #-1]! |
| mov x0, x3 |
| cbnz x3, 0b |
| |
| ldrb w0, [x1] |
| cbnz w0, 1f |
| mov w0, #'0' // Print "0" for 0, not "" |
| strb w0, [x1, #-1]! |
| |
| 1: mov x0, x1 |
| bl puts |
| |
| ldr x30, [sp], #32 |
| ret |
| endfunction |
| .globl putdec |
| |
| // Print an unsigned decimal number x0 to stdout, followed by a newline |
| // Clobbers x0-x5,x8 |
| function putdecn |
| mov x5, x30 |
| |
| bl putdec |
| mov x0, #'\n' |
| bl putc |
| |
| ret x5 |
| endfunction |
| .globl putdecn |
| |
| // Fill x1 bytes starting at x0 with 0. |
| // Clobbers x1, x2. |
| function memclr |
| mov w2, #0 |
| endfunction |
| .globl memclr |
| // fall through to memfill |
| |
| // Trivial memory fill: fill x1 bytes starting at address x0 with byte w2 |
| // Clobbers x1 |
| function memfill |
| cmp x1, #0 |
| b.eq 1f |
| |
| 0: strb w2, [x0], #1 |
| subs x1, x1, #1 |
| b.ne 0b |
| |
| 1: ret |
| endfunction |
| .globl memfill |
| |
| // w0: signal number |
| // x1: sa_action |
| // w2: sa_flags |
| // Clobbers x0-x6,x8 |
| function setsignal |
| str x30, [sp, #-((sa_sz + 15) / 16 * 16 + 16)]! |
| |
| mov w4, w0 |
| mov x5, x1 |
| mov w6, w2 |
| |
| add x0, sp, #16 |
| mov x1, #sa_sz |
| bl memclr |
| |
| mov w0, w4 |
| add x1, sp, #16 |
| str w6, [x1, #sa_flags] |
| str x5, [x1, #sa_handler] |
| mov x2, #0 |
| mov x3, #sa_mask_sz |
| mov x8, #__NR_rt_sigaction |
| svc #0 |
| |
| cbz w0, 1f |
| |
| puts "sigaction failure\n" |
| b abort |
| |
| 1: ldr x30, [sp], #((sa_sz + 15) / 16 * 16 + 16) |
| ret |
| endfunction |
| |
| |
| function tickle_handler |
| // Perhaps collect GCSPR_EL0 here in future? |
| ret |
| endfunction |
| |
| function terminate_handler |
| mov w21, w0 |
| mov x20, x2 |
| |
| puts "Terminated by signal " |
| mov w0, w21 |
| bl putdec |
| puts ", no error\n" |
| |
| mov x0, #0 |
| mov x8, #__NR_exit |
| svc #0 |
| endfunction |
| |
| function segv_handler |
| // stash the siginfo_t * |
| mov x20, x1 |
| |
| // Disable GCS, we don't want additional faults logging things |
| mov x0, PR_SET_SHADOW_STACK_STATUS |
| mov x1, xzr |
| mov x2, xzr |
| mov x3, xzr |
| mov x4, xzr |
| mov x5, xzr |
| mov x8, #__NR_prctl |
| svc #0 |
| |
| puts "Got SIGSEGV code " |
| |
| ldr x21, [x20, #si_code] |
| mov x0, x21 |
| bl putdec |
| |
| // GCS faults should have si_code SEGV_CPERR |
| cmp x21, #SEGV_CPERR |
| bne 1f |
| |
| puts " (GCS violation)" |
| 1: |
| mov x0, '\n' |
| bl putc |
| b abort |
| endfunction |
| |
| // Recurse x20 times |
| .macro recurse id |
| function recurse\id |
| stp x29, x30, [sp, #-16]! |
| mov x29, sp |
| |
| cmp x20, 0 |
| beq 1f |
| sub x20, x20, 1 |
| bl recurse\id |
| |
| 1: |
| ldp x29, x30, [sp], #16 |
| |
| // Do a syscall immediately prior to returning to try to provoke |
| // scheduling and migration at a point where coherency issues |
| // might trigger. |
| mov x8, #__NR_getpid |
| svc #0 |
| |
| ret |
| endfunction |
| .endm |
| |
| // Generate and use two copies so we're changing the GCS contents |
| recurse 1 |
| recurse 2 |
| |
| .globl _start |
| function _start |
| // Run with GCS |
| mov x0, PR_SET_SHADOW_STACK_STATUS |
| mov x1, PR_SHADOW_STACK_ENABLE |
| mov x2, xzr |
| mov x3, xzr |
| mov x4, xzr |
| mov x5, xzr |
| mov x8, #__NR_prctl |
| svc #0 |
| cbz x0, 1f |
| puts "Failed to enable GCS\n" |
| b abort |
| 1: |
| |
| mov w0, #SIGTERM |
| adr x1, terminate_handler |
| mov w2, #SA_SIGINFO |
| bl setsignal |
| |
| mov w0, #SIGUSR1 |
| adr x1, tickle_handler |
| mov w2, #SA_SIGINFO |
| orr w2, w2, #SA_NODEFER |
| bl setsignal |
| |
| mov w0, #SIGSEGV |
| adr x1, segv_handler |
| mov w2, #SA_SIGINFO |
| orr w2, w2, #SA_NODEFER |
| bl setsignal |
| |
| puts "Running\n" |
| |
| loop: |
| // Small recursion depth so we're frequently flipping between |
| // the two recursors and changing what's on the stack |
| mov x20, #5 |
| bl recurse1 |
| mov x20, #5 |
| bl recurse2 |
| b loop |
| endfunction |
| |
| abort: |
| mov x0, #255 |
| mov x8, #__NR_exit |
| svc #0 |