blob: abfcee6f56a41becc8fb6600a113fda0e8975aab [file] [log] [blame]
/* Test program for 64-Bit Microsoft to System V function calls.
Copyright (C) 2016-2017 Free Software Foundation, Inc.
Contributed by Daniel Santos <daniel.santos@pobox.com>
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC 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 General Public License for more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. */
/* This is a single-threaded test program for Microsoft 64-bit ABI functions.
It is aimed at verifying correctness of pro/epilogues of ms_abi functions
that call sysv_abi functions to assure clobbered registers are properly
saved and restored and attempt to detect any flaws in the behavior of these
functions. The following variants are tested:
* Either uses hard frame pointer, re-aligns the stack or neither,
* Uses alloca (and thus DRAP) or not,
* Uses sibling call optimization or not,
* Uses variable argument list or not, and
* Has shrink-wrapped code or not.
In addition, an ms_abi function is generated for each of these combinations
clobbering each unique combination additional registers (excluding BP when
a frame pointer is used). Shrink-wrap variants are called in a way that
both the fast and slow path are used. Re-aligned variants are called with
an aligned and mis-aligned stack.
Each ms_abi function is called via an assembly stub that first saves all
volatile registers and fills them with random values. The ms_abi function
is then called. After the function returns, the value of all volatile
registers is verified against the random data and then restored. */
/* { dg-do run } */
/* { dg-additional-sources "do-test.S" } */
/* { dg-additional-options "-Wall" } */
/* { dg-require-effective-target alloca } */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <stdint.h>
#include <stdarg.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#if !defined(__x86_64__) || !defined(__SSE2__)
# error Test only valid on x86_64 with -msse2
#endif
enum reg_data_sets
{
REG_SET_SAVE,
REG_SET_INPUT,
REG_SET_OUTPUT,
REG_SET_COUNT
};
enum flags
{
FLAG_ALLOCA = 0x01000000,
FLAG_SIBCALL = 0x02000000,
FLAG_SHRINK_WRAP_FAST_PATH = 0x08000000,
FLAG_SHRINK_WRAP_SLOW_PATH = 0x0c000000,
};
enum alignment_option
{
ALIGNMENT_NOT_TESTED,
ALIGNMENT_ALIGNED,
ALIGNMENT_MISALIGNED,
ALIGNMENT_COUNT,
};
enum shrink_wrap_option
{
SHRINK_WRAP_NONE,
SHRINK_WRAP_FAST_PATH,
SHRINK_WRAP_SLOW_PATH,
SHRINK_WRAP_COUNT
};
union regdata {
struct {
__uint128_t sseregs[10];
union {
uint64_t intregs[8];
struct {
uint64_t rsi;
uint64_t rdi;
uint64_t rbx;
uint64_t rbp;
uint64_t r12;
uint64_t r13;
uint64_t r14;
uint64_t r15;
};
};
};
uint32_t u32_arr[56];
} __attribute__((aligned (16)));
struct test_data
{
union regdata regdata[REG_SET_COUNT];
void *fn;
void *retaddr;
const char *name;
enum alignment_option alignment;
enum shrink_wrap_option shrink_wrap;
long ret_expected;
} test_data;
static int shrink_wrap_global;
static void __attribute((sysv_abi)) do_tests ();
static void init_test (void *fn, const char *name,
enum alignment_option alignment,
enum shrink_wrap_option shrink_wrap, long ret_expected);
static void check_results (long ret);
static __attribute__((ms_abi)) long do_sibcall (long arg);
static __attribute__((ms_abi)) long
(*const volatile do_sibcall_noinfo) (long) = do_sibcall;
/* Defines do_tests (). */
#include "ms-sysv-generated.h"
static int arbitrarily_fail;
static const char *argv0;
#define PASTE_STR2(a) #a
#define PASTE_STR1(a, b) PASTE_STR2(a ## b)
#define PASTE_STR(a, b) PASTE_STR1(a, b)
#ifdef __USER_LABEL_PREFIX__
# define ASMNAME(name) PASTE_STR(__USER_LABEL_PREFIX__, name)
#else
# define ASMNAME(name) #name
#endif
#ifdef __MACH__
# define LOAD_TEST_DATA_ADDR(dest) \
" mov " ASMNAME(test_data) "@GOTPCREL(%%rip), " dest "\n"
#else
# define LOAD_TEST_DATA_ADDR(dest) \
" lea " ASMNAME(test_data) "(%%rip), " dest "\n"
#endif
#define TEST_DATA_OFFSET(f) ((int)__builtin_offsetof(struct test_data, f))
void __attribute__((naked))
do_test_body (void)
{__asm__ (
" # rax, r10 and r11 are usable here.\n"
"\n"
" # Save registers.\n"
LOAD_TEST_DATA_ADDR("%%rax")
" lea %p0(%%rax), %%r10\n"
" call " ASMNAME(regs_to_mem) "\n"
"\n"
" # Load registers with random data.\n"
" lea %p1(%%rax), %%r10\n"
" call " ASMNAME(mem_to_regs) "\n"
"\n"
" # Pop and save original return address.\n"
" pop %%r10\n"
" mov %%r10, %p4(%%rax)\n"
"\n"
" # Call the test function, after which rcx, rdx and r8-11\n"
" # become usable.\n"
" lea %p3(%%rax), %%rax\n"
" call *(%%rax)\n"
"\n"
" # Store resulting register values.\n"
LOAD_TEST_DATA_ADDR("%%rcx")
" lea %p2(%%rcx), %%r10\n"
" call " ASMNAME(regs_to_mem) "\n"
"\n"
" # Push the original return address.\n"
" lea %p4(%%rcx), %%r10\n"
" push (%%r10)\n"
"\n"
" # Restore registers.\n"
" lea %p0(%%rcx), %%r10\n"
" call " ASMNAME(mem_to_regs) "\n"
"\n"
" retq\n"
::
"i"(TEST_DATA_OFFSET(regdata[REG_SET_SAVE])),
"i"(TEST_DATA_OFFSET(regdata[REG_SET_INPUT])),
"i"(TEST_DATA_OFFSET(regdata[REG_SET_OUTPUT])),
"i"(TEST_DATA_OFFSET(fn)),
"i"(TEST_DATA_OFFSET(retaddr)) : "memory");
}
static void __attribute__((noinline))
init_test (void *fn, const char *name, enum alignment_option alignment,
enum shrink_wrap_option shrink_wrap, long ret_expected)
{
int i;
union regdata *data = &test_data.regdata[REG_SET_INPUT];
assert (alignment < ALIGNMENT_COUNT);
assert (shrink_wrap < SHRINK_WRAP_COUNT);
memset (&test_data, 0, sizeof (test_data));
for (i = 55; i >= 0; --i)
data->u32_arr[i] = (uint32_t)lrand48 ();
test_data.fn = fn;
test_data.name = name;
test_data.alignment = alignment;
test_data.shrink_wrap = shrink_wrap;
test_data.ret_expected = ret_expected;
switch (shrink_wrap)
{
case SHRINK_WRAP_NONE:
case SHRINK_WRAP_COUNT:
break;
case SHRINK_WRAP_FAST_PATH:
shrink_wrap_global = FLAG_SHRINK_WRAP_FAST_PATH;
break;
case SHRINK_WRAP_SLOW_PATH:
shrink_wrap_global = FLAG_SHRINK_WRAP_SLOW_PATH;
break;
}
}
static const char *alignment_str[ALIGNMENT_COUNT] =
{
"", "aligned", "misaligned"
};
static const char *shrink_wrap_str[SHRINK_WRAP_COUNT] =
{
"", "shrink-wrap fast path", "shrink-wrap slow path"
};
static const char *test_descr ()
{
static char buffer[0x400];
if (test_data.alignment || test_data.shrink_wrap)
snprintf (buffer, sizeof (buffer) - 1, "`%s' (%s%s%s)",
test_data.name,
alignment_str[test_data.alignment],
(test_data.alignment && test_data.shrink_wrap ? ", " : ""),
shrink_wrap_str[test_data.shrink_wrap]);
else
snprintf (buffer, sizeof (buffer) - 1, "`%s'", test_data.name);
return buffer;
}
static const char *regnames[] = {
"XMM6",
"XMM7",
"XMM8",
"XMM9",
"XMM10",
"XMM11",
"XMM12",
"XMM13",
"XMM14",
"XMM15",
"RSI",
"RDI",
"RBX",
"RBP",
"R12",
"R13",
"R14",
"R15",
};
static void print_header (int *header_printed)
{
if (!*header_printed)
fprintf (stderr, " %-35s %-35s\n", "Expected", "Got");
*header_printed = 1;
}
static int compare_reg128 (const __uint128_t *a, const __uint128_t *b,
const char *name, int *header_printed)
{
if (!memcmp (a, b, sizeof (*a)))
return 0;
else
{
long ha = *((long*)a);
long la = *((long*)a + 16);
long hb = *((long*)b);
long lb = *((long*)a + 16);
print_header (header_printed);
fprintf (stderr, "%-5s: 0x%016lx %016lx != 0x%016lx %016lx\n",
name, ha, la, hb, lb);
return 1;
}
}
static int compare_reg64 (long a, long b, const char *name,
int *header_printed)
{
if (a == b)
return 0;
else
{
print_header (header_printed);
fprintf (stderr, "%s: 0x%016lx != 0x%016lx\n", name, a, b);
return 1;
}
}
static void __attribute__((noinline)) check_results (long ret)
{
unsigned i;
unsigned bad = 0;
int header_printed = 0;
union regdata *a = &test_data.regdata[REG_SET_INPUT];
union regdata *b = &test_data.regdata[REG_SET_OUTPUT];
a = __builtin_assume_aligned(a, 16);
b = __builtin_assume_aligned(b, 16);
if (arbitrarily_fail) {
uint64_t u64 = lrand48 ();
if (u64 % 100 == 0)
b->u32_arr[u64 % 56] = 0xfdfdfdfd;
}
for (i = 0; i < 10; ++i)
bad |= compare_reg128 (&a->sseregs[i], &b->sseregs[i], regnames[i],
&header_printed);
for (i = 0; i < 8; ++i)
bad |= compare_reg64 (a->intregs[i], b->intregs[i], regnames[i + 10],
&header_printed);
if (ret != test_data.ret_expected)
{
fprintf (stderr, "Wrong return value: got 0x%016lx, expected 0x%016lx\n",
ret, test_data.ret_expected);
bad = 1;
}
if (bad)
{
fprintf (stderr, "Failed on test function %s\n", test_descr ());
raise (SIGTRAP);
exit (-1);
}
}
static __attribute__((ms_abi, noinline)) long do_sibcall (long arg) {
return arg + FLAG_SIBCALL;
}
void usage ()
{
fprintf (stderr, "Usage: %s [-s <seed>] [-f]\n", argv0);
exit (-1);
}
static long long_optarg (const char *optarg, const char *optstr)
{
char *end;
long ret;
errno = 0;
ret = strtol(optarg, &end, 0);
while (isspace (*end))
++end;
if (errno || *end)
{
fprintf (stderr, "ERROR: Bad value for %s: `%s`\n", optstr, optarg);
if (errno)
fprintf (stderr, "%s\n", strerror (errno));
exit (-1);
}
return ret;
}
int main (int argc, char *argv[])
{
long seed = 0;
int c;
argv0 = argv[0];
assert (!((long)&test_data.regdata[REG_SET_SAVE] & 15));
assert (!((long)&test_data.regdata[REG_SET_INPUT] & 15));
assert (!((long)&test_data.regdata[REG_SET_OUTPUT] & 15));
while ((c = getopt (argc, argv, "s:f")) != -1)
{
switch (c)
{
case 's':
seed = long_optarg (optarg, "-s");
break;
case 'f':
arbitrarily_fail = 1;
fprintf (stderr, "NOTE: Aribrary failure enabled (-f).\n");
break;
}
}
srand48 (seed);
do_tests ();
/* Just in case we don't have enough tests to randomly trigger the
failure. */
if (arbitrarily_fail)
return -1;
return 0;
}