From 698a403d32b9d31db3d880572cefffb49c265008 Mon Sep 17 00:00:00 2001 From: Helmut Eller Date: Sat, 11 Oct 2025 20:33:40 +0200 Subject: [PATCH] Recognize some Amd64 instruction patterns * mps/code/prmci6.c (IsSimpleMov): Actually do something. (IsRexPrefix, DecodeSimpleMov, RexR, RexB, RexX, DecodeDisp32) (SignedInsElt, RegValue, DecodeModRM, DecodeSIB, DecodeCB): New helpers. * mps/code/prmctest.c: New file. * mps/code/comm.gmk: Add prmctest. * mps/tool/testcases.txt: Here too. --- mps/code/comm.gmk | 4 + mps/code/prmci6.c | 194 +++++++++++++++++++++++++++-- mps/code/prmctest.c | 276 +++++++++++++++++++++++++++++++++++++++++ mps/tool/testcases.txt | 1 + 4 files changed, 464 insertions(+), 11 deletions(-) create mode 100644 mps/code/prmctest.c diff --git a/mps/code/comm.gmk b/mps/code/comm.gmk index c70d33e5615..488d28ff1fb 100644 --- a/mps/code/comm.gmk +++ b/mps/code/comm.gmk @@ -297,6 +297,7 @@ TEST_TARGETS=\ mv2test$(EXEEXT) \ nailboardtest$(EXEEXT) \ poolncv$(EXEEXT) \ + prmctest$(EXEEXT) \ qs$(EXEEXT) \ sacss$(EXEEXT) \ segsmss$(EXEEXT) \ @@ -568,6 +569,9 @@ $(PFM)/$(VARIETY)/nailboardtest$(EXEEXT): $(PFM)/$(VARIETY)/nailboardtest.o \ $(PFM)/$(VARIETY)/poolncv$(EXEEXT): $(PFM)/$(VARIETY)/poolncv.o \ $(POOLNOBJ) $(TESTLIBOBJ) $(PFM)/$(VARIETY)/mps.a +$(PFM)/$(VARIETY)/prmctest$(EXEEXT): $(PFM)/$(VARIETY)/prmctest.o \ + $(TESTLIBOBJ) $(PFM)/$(VARIETY)/mps.a + $(PFM)/$(VARIETY)/qs$(EXEEXT): $(PFM)/$(VARIETY)/qs.o \ $(TESTLIBOBJ) $(PFM)/$(VARIETY)/mps.a diff --git a/mps/code/prmci6.c b/mps/code/prmci6.c index 31887f7cae0..ff498245653 100644 --- a/mps/code/prmci6.c +++ b/mps/code/prmci6.c @@ -34,25 +34,197 @@ SRCID(prmci6, "$Id$"); #error "prmci6.c is specific to MPS_ARCH_I6" #endif +/* DecodeCB -- Decode an Intel x86 control byte into Hi, Medium & Low + fields */ -static Bool IsSimpleMov(Size *inslenReturn, - MRef *srcReturn, - MRef *destReturn, - MutatorContext context) +static void DecodeCB(size_t *hReturn, size_t *mReturn, size_t *lReturn, + Byte op) +{ + *lReturn = op & 7; + *mReturn = (op >> 3) & 7; + *hReturn = (op >> 6) & 3; +} + + +/* DecodeSIB -- Decode a Scale Index Base byte for an Intel x86 + instruction */ + +static void DecodeSIB(size_t *sReturn, size_t *iReturn, size_t *bReturn, + Byte op) +{ + DecodeCB(sReturn, iReturn, bReturn, op); +} + + +/* DecodeModRM -- Decode a ModR/M byte for an Intel x86 instruction */ + +static void DecodeModRM(size_t *modReturn, size_t *rReturn, + size_t *mReturn, Byte op) +{ + DecodeCB(modReturn, rReturn, mReturn, op); +} + + +/* RegValue -- Return the value of a machine register from a context */ + +static Word RegValue(MutatorContext context, size_t regnum) +{ + MRef addr; + + addr = Prmci6AddressHoldingReg(context, regnum); + return *addr; +} + + +/* Return a byte element of an instruction vector as a + * Word value, with sign extension + */ +static Word SignedInsElt(Byte insvec[], Count i) +{ + signed char eltb; + + eltb = ((signed char *)insvec)[i]; + return (Word)eltb; +} + +static signed DecodeDisp32(Byte *bytes) +{ + unsigned r = 0; + size_t i; + for (i = 0; i < 4; i++) r |= bytes[i] << (i * 8); + return r; +} + +static Word RexR(Byte rex) { return (rex >> 2) & 1; } +static Word RexB(Byte rex) { return (rex >> 0) & 1; } +static Word RexX(Byte rex) { return (rex >> 1) & 1; } + +static Bool DecodeSimpleMov(size_t *regnumReturn, MRef *memReturn, + Size *inslenReturn, MutatorContext context, + Byte insvec[]) +{ + Byte rex = insvec[0]; + Byte modRM = insvec[2]; + Word base, disp = 0, scaled_index = 0; + size_t mod, reg, rm; + DecodeModRM(&mod, ®, &rm, modRM); + switch (mod) { + case 0: + switch (rm) { + default: + *inslenReturn = 3; + base = RegValue(context, rm | (RexB(rex) << 3)); + goto done; + + case 4: + *inslenReturn = 4; + goto decode_sib; + + case 5: { + Word rip = (Word)insvec; + *inslenReturn = 7; + base = rip + *inslenReturn; + disp = DecodeDisp32(insvec + 3); + goto done; + } + } + + case 1: + switch (rm) { + default: + *inslenReturn = 4; + disp = SignedInsElt(insvec, 3); + base = RegValue(context, rm | (RexB(rex) << 3)); + goto done; + + case 4: + *inslenReturn = 5; + disp = SignedInsElt(insvec, 4); + goto decode_sib; + } + + case 2: + switch (rm) { + default: + *inslenReturn = 7; + disp = DecodeDisp32(insvec + 3); + base = RegValue(context, rm | (RexB(rex) << 3)); + goto done; + + case 4: + *inslenReturn = 8; + disp = DecodeDisp32(insvec + 4); + goto decode_sib; + } + + case 3: + return FALSE; + + default: + NOTREACHED; + } + NOTREACHED; + +decode_sib: { + size_t s, i, b; + DecodeSIB(&s, &i, &b, insvec[3]); + base = RegValue(context, b | (RexB(rex) << 3)); + if (i == 4 && !RexX(rex)) + scaled_index = 0; + else + scaled_index = (RegValue(context, i | RexX(rex) << 3) << s); +} + +done: + *memReturn = (MRef)(base + disp + scaled_index); + *regnumReturn = reg | (RexR(rex) << 3); + return TRUE; +} + +static Bool IsRexPrefix(Byte b) { return (b >> 4) == 0x4; } + +static Bool IsSimpleMov(Size *inslenReturn, MRef *srcReturn, + MRef *destReturn, MutatorContext context) { Byte *insvec; + size_t regnum; + MRef mem; MRef faultmem; Prmci6DecodeFaultContext(&faultmem, &insvec, context); - /* Unimplemented */ - UNUSED(inslenReturn); - UNUSED(srcReturn); - UNUSED(destReturn); + + if (IsRexPrefix(insvec[0])) { + switch (insvec[1]) { + case 0x8b: /* MOV r64, r/m64 */ + if (DecodeSimpleMov(®num, &mem, inslenReturn, context, + insvec)) { + AVER(faultmem == mem); /* Ensure computed address + matches exception */ + *srcReturn = mem; + *destReturn = Prmci6AddressHoldingReg(context, regnum); + return TRUE; + } else + return FALSE; + + case 0x89: /* MOV r/m64, r64 */ + if (DecodeSimpleMov(®num, &mem, inslenReturn, context, + insvec)) { + AVER(faultmem == mem); /* Ensure computed address + matches exception */ + *destReturn = mem; + *srcReturn = Prmci6AddressHoldingReg(context, regnum); + return TRUE; + } else + return FALSE; + + default: + return FALSE; + } + } return FALSE; } - Bool MutatorContextCanStepInstruction(MutatorContext context) { Size inslen; @@ -62,7 +234,7 @@ Bool MutatorContextCanStepInstruction(MutatorContext context) AVERT(MutatorContext, context); /* .assume.null */ - if(IsSimpleMov(&inslen, &src, &dest, context)) { + if (IsSimpleMov(&inslen, &src, &dest, context)) { return TRUE; } @@ -79,7 +251,7 @@ Res MutatorContextStepInstruction(MutatorContext context) AVERT(MutatorContext, context); /* .assume.null */ - if(IsSimpleMov(&inslen, &src, &dest, context)) { + if (IsSimpleMov(&inslen, &src, &dest, context)) { *dest = *src; Prmci6StepOverIns(context, inslen); return ResOK; diff --git a/mps/code/prmctest.c b/mps/code/prmctest.c new file mode 100644 index 00000000000..a876bb35ed6 --- /dev/null +++ b/mps/code/prmctest.c @@ -0,0 +1,276 @@ +/* prmctest.c: TEST INSTRUCTION EMULATION + * + * $Id$ + * Copyright (c) 2001-2020 Ravenbrook Limited. See end of file for license. + */ + +#include "prmc.h" +#include "prmcix.h" +#include "testlib.h" + +#include + +#if defined MPS_PF_LII6GC && !defined CONFIG_PF_ANSI + +#include +#include +#include + +#define VERBOSE 0 + +#pragma GCC diagnostic ignored "-Wpedantic" + +static void **asmcode(int) __attribute__((optimize(0))); +static void **asmcode(int f) +{ + static void *labels[] = {&&start, &&end}; + if (f) + return labels; +start: + __asm__( + "mov (%rax),%rax\n" + "mov (%rcx),%r15\n" + "mov (%r12),%rdx\n" + "mov (%r13),%r8\n" + "mov mem(%rip),%r8\n" + "mov 0x8(%r14),%rsi\n" + "mov 0xabcdef(%rax),%rax\n" + "mov 0x1(%r12),%r8\n" + "mov (%rax,%rdx,8),%rsi\n" + "mov (%r8,%r9,1),%r8\n" + "mov (%r8,%r12,2),%r8\n" + "mov 0x10(%rax,%rdx,8),%rsi\n" + /* TODO: "movq (%rax),%xmm1\n */ + ); +end: + goto start; +} + + +static mps_word_t mem[8]; + +static void testMovRm64ToR64(MutatorContext ctx, size_t srcreg, + size_t disp, size_t scale, size_t idxreg, + size_t dstreg, size_t len) +{ + mps_word_t *gregs = (void *)ctx->ucontext->uc_mcontext.gregs; + siginfo_t *siginfo = ctx->info; + size_t ip0 = gregs[REG_RIP]; + const size_t some_value = (size_t)rnd_addr(); + memset(gregs, 0, sizeof(ctx->ucontext->uc_mcontext.gregs)); + memset(mem, 0, sizeof(mem)); + gregs[REG_RIP] = ip0; + if (scale) { + assert(idxreg != srcreg); + gregs[idxreg] = rnd(); + } + if (srcreg != REG_RIP) { + gregs[srcreg] + = (size_t)((Byte *)mem - disp - gregs[idxreg] * scale); + } + siginfo->si_addr = mem; + mem[0] = some_value; + if (!(dstreg == srcreg || (scale && dstreg == idxreg))) + assert(gregs[dstreg] == 0); + assert(gregs[dstreg] != some_value); + assert(MutatorContextCanStepInstruction(ctx)); + assert(MutatorContextStepInstruction(ctx) == MPS_RES_OK); + assert(gregs[dstreg] == some_value); + assert(mem[0] == some_value); + assert(gregs[REG_RIP] - ip0 == len); +} + +struct regname { + size_t num; + const char *name; +}; + +static struct regname const gprs[] = { + {REG_R8, "%r8"}, {REG_R9, "%r9"}, {REG_R10, "%r10"}, + {REG_R11, "%r11"}, {REG_R12, "%r12"}, {REG_R13, "%r13"}, + {REG_R14, "%r14"}, {REG_R15, "%r15"}, {REG_RDI, "%rdi"}, + {REG_RSI, "%rsi"}, {REG_RBP, "%rbp"}, {REG_RBX, "%rbx"}, + {REG_RDX, "%rdx"}, {REG_RAX, "%rax"}, {REG_RCX, "%rcx"}, + {REG_RSP, "%rsp"}, {REG_RIP, "%rip"}, +}; + +static const char *regname(size_t regnum) +{ + size_t i; + for (i = 0; i < NELEMS(gprs); i++) + if (gprs[i].num == regnum) + return gprs[i].name; + assert(0); +} + +static char *open_tmpfile(const char *mode, FILE **f) +{ + static const char *template = "/tmp/prmc-XXXXXX"; + char buf[32]; + int fd; + strcpy(buf, template); + fd = mkstemp(buf); + assert(fd != -1); + *f = fdopen(fd, mode); + assert(*f != NULL); + return strdup(buf); +} + +static Byte *genCode(const char *txt, size_t *len) +{ + FILE *a, *o, *s; + char *aname = open_tmpfile("wb", &a); + char *oname = open_tmpfile("rb", &o); + char *sname = open_tmpfile("rb", &s); + char cmd[256]; + size_t size, offset; + Byte *code; + if (VERBOSE) + fprintf(stderr, "%s", txt); + fprintf(a, "%s", txt); + fflush(a); + snprintf(cmd, sizeof cmd, + "as %s -o %s" + " && objdump -h %s " + " | awk '/text/ " + " { print $2 \" size: \" $3 \" offset: \" $6 }'" + " >%s", + aname, oname, oname, sname); + assert(system(cmd) == 0); + assert(fscanf(s, ".text size: %lx offset: %lx\n", &size, &offset) + == 2); + assert(fseek(o, offset, SEEK_SET) == 0); + code = malloc(size); + assert(code); + assert(fread(code, 1, size, o) == size); + remove(aname); + remove(oname); + remove(sname); + free(aname); + free(oname); + free(sname); + fclose(a); + fclose(o); + fclose(s); + *len = size; + return code; +} + +static void genTest(MutatorContext ctx, size_t base, int32_t disp, + size_t scale, size_t index, size_t dst) +{ + char buf[1024]; + size_t len; + Byte *code; + if (scale) + snprintf(buf, sizeof buf, "mov %d(%s,%s,%ld),%s\n", disp, + regname(base), regname(index), scale, regname(dst)); + else + snprintf(buf, sizeof buf, "mov %d(%s),%s\n", disp, regname(base), + regname(dst)); + code = genCode(buf, &len); + ctx->ucontext->uc_mcontext.gregs[REG_RIP] = (size_t)code; + testMovRm64ToR64(ctx, base, disp, scale, index, dst, len); +} + +static int32_t displacemts[] = {0, -1, 255, -256, 256, 0xabcdef01}; + +static Bool filterRandomly(double probability) +{ + return probability < rnd_double(); +} + +static void genTests(MutatorContext ctx) +{ + const size_t NSCALEBITS = 3; + const size_t NTESTS = NELEMS(gprs) * (NSCALEBITS + 1) * NELEMS(gprs) + * NELEMS(gprs) * NELEMS(displacemts); + const double probability = (double)500 / NTESTS; + size_t b, sbits, i, d, e; + for (b = 0; b < NELEMS(gprs); b++) + for (sbits = 0; sbits < NSCALEBITS + 1; sbits++) + for (i = 0; i < NELEMS(gprs); i++) + for (d = 0; d < NELEMS(gprs); d++) { + for (e = 0; e < NELEMS(displacemts); e++) { + size_t base = gprs[b].num; + size_t index = gprs[i].num; + size_t dst = gprs[d].num; + int32_t disp = displacemts[e]; + size_t scale = sbits ? (1 << sbits) : 0; + if (dst == REG_RIP || (!sbits && i) + || (sbits && base == index) || index == REG_RSP + || base == REG_RIP || index == REG_RIP + || filterRandomly(probability)) + continue; + genTest(ctx, base, disp, scale, index, dst); + } + } +} + +static void runTests(void) +{ + siginfo_t siginfo; + struct ucontext_t ucontext; + struct MutatorContextStruct ctx; + MutatorContextInitFault(&ctx, &siginfo, &ucontext); + ucontext.uc_mcontext.gregs[REG_RIP] = (size_t)(asmcode(1)[0]); + + testMovRm64ToR64(&ctx, REG_RAX, 0, 0, 0, REG_RAX, 3); + testMovRm64ToR64(&ctx, REG_RCX, 0, 0, 0, REG_R15, 3); + testMovRm64ToR64(&ctx, REG_R12, 0, 0, 0, REG_RDX, 4); + testMovRm64ToR64(&ctx, REG_R13, 0, 0, 0, REG_R8, 4); + testMovRm64ToR64(&ctx, REG_RIP, 8, 0, 0, REG_R8, 7); + testMovRm64ToR64(&ctx, REG_R14, 8, 0, 0, REG_RSI, 4); + testMovRm64ToR64(&ctx, REG_RAX, 0xabcdef, 0, 0, REG_RAX, 7); + testMovRm64ToR64(&ctx, REG_R12, 0x1, 0, 0, REG_R8, 5); + testMovRm64ToR64(&ctx, REG_RAX, 0, 8, REG_RDX, REG_RSI, 4); + testMovRm64ToR64(&ctx, REG_R8, 0, 1, REG_R9, REG_R8, 4); + testMovRm64ToR64(&ctx, REG_R8, 0, 2, REG_R12, REG_R8, 4); + testMovRm64ToR64(&ctx, REG_RAX, 0x10, 8, REG_RDX, REG_RSI, 5); + + genTests(&ctx); +} + +#endif + +int main(int argc, char *argv[]) +{ + testlib_init(argc, argv); + +#if defined MPS_PF_LII6GC && !defined CONFIG_PF_ANSI + runTests(); +#endif + + printf("%s: Conclusion: Failed to find any defects.\n", argv[0]); + return 0; +} + + +/* C. COPYRIGHT AND LICENSE + * + * Copyright (C) 2001-2020 Ravenbrook Limited . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/mps/tool/testcases.txt b/mps/tool/testcases.txt index bf6b37dd74b..eb85a611541 100644 --- a/mps/tool/testcases.txt +++ b/mps/tool/testcases.txt @@ -35,6 +35,7 @@ mpsicv mv2test nailboardtest poolncv +prmctest qs sacss segsmss