Browse Source
A conflict exists when any GPR is written by multiple instructions and at least one write is unconditional. This catches (1) two unconditional writes to the same GPR and (2) an unconditional write combined with a predicated write. Add HEX_CAUSE_REG_WRITE_CONFLICT and map it to SIGILL. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2696 Reviewed-by: Taylor Simpson <ltaylorsimpson@gmail.com> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>master
8 changed files with 248 additions and 2 deletions
@ -0,0 +1,169 @@ |
|||
/*
|
|||
* Test detection of multiple writes to the same register. |
|||
* |
|||
* Ported from the system test (tests/tcg/hexagon/system/multiple_writes.c). |
|||
* In linux-user mode, duplicate GPR writes are detected at translate time |
|||
* and raise SIGILL when at least one conflicting write is unconditional. |
|||
* Purely predicated duplicate writes (e.g., complementary if/if-not) are |
|||
* legal and are not flagged statically. |
|||
* |
|||
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. |
|||
* SPDX-License-Identifier: GPL-2.0-or-later |
|||
*/ |
|||
|
|||
#include <assert.h> |
|||
#include <signal.h> |
|||
#include <stdio.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
#include <unistd.h> |
|||
|
|||
static void *resume_pc; |
|||
|
|||
static void handle_sigill(int sig, siginfo_t *info, void *puc) |
|||
{ |
|||
ucontext_t *uc = (ucontext_t *)puc; |
|||
|
|||
if (sig != SIGILL) { |
|||
_exit(EXIT_FAILURE); |
|||
} |
|||
|
|||
uc->uc_mcontext.r0 = SIGILL; |
|||
uc->uc_mcontext.pc = (unsigned long)resume_pc; |
|||
} |
|||
|
|||
/*
|
|||
* Unconditional pair write overlapping a single write: |
|||
* { r1:0 = add(r3:2, r3:2); r1 = add(r0, r1) } |
|||
* R1 is written by both instructions. This is invalid and must raise SIGILL. |
|||
*/ |
|||
static int test_static_pair_overlap(void) |
|||
{ |
|||
int sig; |
|||
|
|||
asm volatile( |
|||
"r0 = #0\n" |
|||
"r1 = ##1f\n" |
|||
"memw(%1) = r1\n" |
|||
".word 0xd30242e0\n" /* r1:0 = add(r3:2, r3:2), parse=01 */ |
|||
".word 0xf300c101\n" /* r1 = add(r0, r1), parse=11 (end) */ |
|||
"1:\n" |
|||
"%0 = r0\n" |
|||
: "=r"(sig) |
|||
: "r"(&resume_pc) |
|||
: "r0", "r1", "memory"); |
|||
|
|||
return sig; |
|||
} |
|||
|
|||
/*
|
|||
* Two predicated writes under complementary predicates: |
|||
* { if (p0) r0 = r2; if (!p0) r0 = r3 } |
|||
* This is architecturally valid: only one write executes at runtime. |
|||
* Must NOT raise SIGILL; the result should reflect the executed branch. |
|||
*/ |
|||
static int test_legal_predicated(void) |
|||
{ |
|||
int result; |
|||
|
|||
asm volatile( |
|||
"r0 = #0\n" |
|||
"r1 = ##1f\n" |
|||
"memw(%1) = r1\n" |
|||
"r2 = #7\n" |
|||
"r3 = #13\n" |
|||
"p0 = cmp.eq(r2, r2)\n" |
|||
"{\n" |
|||
" if (p0) r0 = r2\n" |
|||
" if (!p0) r0 = r3\n" |
|||
"}\n" |
|||
"1:\n" |
|||
"%0 = r0\n" |
|||
: "=r"(result) |
|||
: "r"(&resume_pc) |
|||
: "r0", "r1", "r2", "r3", "p0", "memory"); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
/*
|
|||
* Mixed: unconditional + predicated writes to the same register: |
|||
* { if (p0) r1 = add(r0, #0); if (!p0) r1 = add(r0, #0); |
|||
* r1 = add(r0, #0) } |
|||
* The unconditional write always conflicts with the predicated writes. |
|||
* Must raise SIGILL. |
|||
*/ |
|||
static int test_mixed_writes(void) |
|||
{ |
|||
int sig; |
|||
|
|||
asm volatile( |
|||
"r0 = #0\n" |
|||
"r1 = ##1f\n" |
|||
"memw(%1) = r1\n" |
|||
"p0 = cmp.eq(r0, r0)\n" |
|||
".word 0x7e204021\n" /* if (p0) r1 = add(r0, #0), parse=01 */ |
|||
".word 0x7ea04021\n" /* if (!p0) r1 = add(r0, #0), parse=01 */ |
|||
".word 0x7800c021\n" /* r1 = add(r0, #0), parse=11 (end) */ |
|||
"1:\n" |
|||
"%0 = r0\n" |
|||
: "=r"(sig) |
|||
: "r"(&resume_pc) |
|||
: "r0", "r1", "p0", "memory"); |
|||
|
|||
return sig; |
|||
} |
|||
|
|||
/*
|
|||
* Zero encoding (issue #2696): |
|||
* The encoding 0x00000000 decodes as a duplex with parse bits |
|||
* [15:14] = 0b00: |
|||
* slot1: SL1_loadri_io R0 = memw(R0+#0x0) |
|||
* slot0: SL1_loadri_io R0 = memw(R0+#0x0) |
|||
* |
|||
* Both sub-instructions write R0 unconditionally, which is an invalid |
|||
* packet. This tests what happens when we jump to zeroed memory. |
|||
* Must raise SIGILL. |
|||
*/ |
|||
static int test_zero(void) |
|||
{ |
|||
int sig; |
|||
|
|||
asm volatile( |
|||
"r0 = #0\n" |
|||
"r1 = ##1f\n" |
|||
"memw(%1) = r1\n" |
|||
".word 0x00000000\n" |
|||
"1:\n" |
|||
"%0 = r0\n" |
|||
: "=r"(sig) |
|||
: "r"(&resume_pc) |
|||
: "r0", "r1", "memory"); |
|||
|
|||
return sig; |
|||
} |
|||
|
|||
int main() |
|||
{ |
|||
struct sigaction act; |
|||
|
|||
memset(&act, 0, sizeof(act)); |
|||
act.sa_sigaction = handle_sigill; |
|||
act.sa_flags = SA_SIGINFO; |
|||
assert(sigaction(SIGILL, &act, NULL) == 0); |
|||
|
|||
/* Legal: complementary predicated writes must not raise SIGILL */ |
|||
assert(test_legal_predicated() == 7); |
|||
|
|||
/* Illegal: unconditional pair + single overlap must raise SIGILL */ |
|||
assert(test_static_pair_overlap() == SIGILL); |
|||
|
|||
/* Illegal: unconditional + predicated writes to same reg must SIGILL */ |
|||
assert(test_mixed_writes() == SIGILL); |
|||
|
|||
/* Illegal: zero encoding = duplex with duplicate dest R0 */ |
|||
assert(test_zero() == SIGILL); |
|||
|
|||
puts("PASS"); |
|||
return EXIT_SUCCESS; |
|||
} |
|||
Loading…
Reference in new issue