root/tools/testing/selftests/powerpc/mm/stack_expansion_ldst.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Test that loads/stores expand the stack segment, or trigger a SEGV, in
 * various conditions.
 *
 * Based on test code by Tom Lane.
 */

#undef NDEBUG
#include <assert.h>

#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#define _KB (1024)
#define _MB (1024 * 1024)

volatile char *stack_top_ptr;
volatile unsigned long stack_top_sp;
volatile char c;

enum access_type {
        LOAD,
        STORE,
};

/*
 * Consume stack until the stack pointer is below @target_sp, then do an access
 * (load or store) at offset @delta from either the base of the stack or the
 * current stack pointer.
 */
__attribute__ ((noinline))
int consume_stack(unsigned long target_sp, unsigned long stack_high, int delta, enum access_type type)
{
        unsigned long target;
        char stack_cur;

        if ((unsigned long)&stack_cur > target_sp)
                return consume_stack(target_sp, stack_high, delta, type);
        else {
                // We don't really need this, but without it GCC might not
                // generate a recursive call above.
                stack_top_ptr = &stack_cur;

#ifdef __powerpc__
                asm volatile ("mr %[sp], %%r1" : [sp] "=r" (stack_top_sp));
#else
                asm volatile ("mov %%rsp, %[sp]" : [sp] "=r" (stack_top_sp));
#endif
                target = stack_high - delta + 1;
                volatile char *p = (char *)target;

                if (type == STORE)
                        *p = c;
                else
                        c = *p;

                // Do something to prevent the stack frame being popped prior to
                // our access above.
                getpid();
        }

        return 0;
}

static int search_proc_maps(char *needle, unsigned long *low, unsigned long *high)
{
        unsigned long start, end;
        static char buf[4096];
        char name[128];
        FILE *f;
        int rc;

        f = fopen("/proc/self/maps", "r");
        if (!f) {
                perror("fopen");
                return -1;
        }

        while (fgets(buf, sizeof(buf), f)) {
                rc = sscanf(buf, "%lx-%lx %*c%*c%*c%*c %*x %*d:%*d %*d %127s\n",
                            &start, &end, name);
                if (rc == 2)
                        continue;

                if (rc != 3) {
                        printf("sscanf errored\n");
                        rc = -1;
                        break;
                }

                if (strstr(name, needle)) {
                        *low = start;
                        *high = end - 1;
                        rc = 0;
                        break;
                }
        }

        fclose(f);

        return rc;
}

int child(unsigned int stack_used, int delta, enum access_type type)
{
        unsigned long low, stack_high;

        assert(search_proc_maps("[stack]", &low, &stack_high) == 0);

        assert(consume_stack(stack_high - stack_used, stack_high, delta, type) == 0);

        printf("Access OK: %s delta %-7d used size 0x%06x stack high 0x%lx top_ptr %p top sp 0x%lx actual used 0x%lx\n",
               type == LOAD ? "load" : "store", delta, stack_used, stack_high,
               stack_top_ptr, stack_top_sp, stack_high - stack_top_sp + 1);

        return 0;
}

static int test_one(unsigned int stack_used, int delta, enum access_type type)
{
        pid_t pid;
        int rc;

        pid = fork();
        if (pid == 0)
                exit(child(stack_used, delta, type));

        assert(waitpid(pid, &rc, 0) != -1);

        if (WIFEXITED(rc) && WEXITSTATUS(rc) == 0)
                return 0;

        // We don't expect a non-zero exit that's not a signal
        assert(!WIFEXITED(rc));

        printf("Faulted:   %s delta %-7d used size 0x%06x signal %d\n",
               type == LOAD ? "load" : "store", delta, stack_used,
               WTERMSIG(rc));

        return 1;
}

// This is fairly arbitrary but is well below any of the targets below,
// so that the delta between the stack pointer and the target is large.
#define DEFAULT_SIZE    (32 * _KB)

static void test_one_type(enum access_type type, unsigned long page_size, unsigned long rlim_cur)
{
        unsigned long delta;

        // We should be able to access anywhere within the rlimit
        for (delta = page_size; delta <= rlim_cur; delta += page_size)
                assert(test_one(DEFAULT_SIZE, delta, type) == 0);

        assert(test_one(DEFAULT_SIZE, rlim_cur, type) == 0);

        // But if we go past the rlimit it should fail
        assert(test_one(DEFAULT_SIZE, rlim_cur + 1, type) != 0);
}

static int test(void)
{
        unsigned long page_size;
        struct rlimit rlimit;

        page_size = getpagesize();
        getrlimit(RLIMIT_STACK, &rlimit);
        printf("Stack rlimit is 0x%llx\n", (unsigned long long)rlimit.rlim_cur);

        printf("Testing loads ...\n");
        test_one_type(LOAD, page_size, rlimit.rlim_cur);
        printf("Testing stores ...\n");
        test_one_type(STORE, page_size, rlimit.rlim_cur);

        printf("All OK\n");

        return 0;
}

#ifdef __powerpc__
#include "utils.h"

int main(void)
{
        return test_harness(test, "stack_expansion_ldst");
}
#else
int main(void)
{
        return test();
}
#endif