qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [RISU PATCH v3 06/18] risugen_x86: add module


From: Jan Bobek
Subject: [Qemu-devel] [RISU PATCH v3 06/18] risugen_x86: add module
Date: Thu, 11 Jul 2019 18:32:48 -0400

risugen_x86.pm is the main backend module for Intel i386 and x86_64
architectures; it orchestrates generation of the test code with
support from the rest of risugen_x86_* modules.

Signed-off-by: Jan Bobek <address@hidden>
---
 risugen_x86.pm | 518 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 518 insertions(+)
 create mode 100644 risugen_x86.pm

diff --git a/risugen_x86.pm b/risugen_x86.pm
new file mode 100644
index 0000000..ae11843
--- /dev/null
+++ b/risugen_x86.pm
@@ -0,0 +1,518 @@
+#!/usr/bin/perl -w
+###############################################################################
+# Copyright (c) 2019 Jan Bobek
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+# Contributors:
+#     Jan Bobek - initial implementation
+###############################################################################
+
+# risugen_x86 -- risugen module for Intel i386/x86_64 architectures
+package risugen_x86;
+
+use strict;
+use warnings;
+
+use risugen_common;
+use risugen_x86_asm;
+use risugen_x86_constraints;
+use risugen_x86_memory;
+
+require Exporter;
+
+our @ISA    = qw(Exporter);
+our @EXPORT = qw(write_test_code);
+
+use constant {
+    RISUOP_COMPARE     => 0,        # compare registers
+    RISUOP_TESTEND     => 1,        # end of test, stop
+    RISUOP_SETMEMBLOCK => 2,        # eax is address of memory block (8192 
bytes)
+    RISUOP_GETMEMBLOCK => 3,        # add the address of memory block to eax
+    RISUOP_COMPAREMEM  => 4,        # compare memory block
+
+    # Maximum alignment restriction permitted for a memory op.
+    MAXALIGN => 64,
+    MEMBLOCK_LEN => 8192,
+};
+
+my $periodic_reg_random = 1;
+my $is_x86_64 = 0;
+
+sub wrap_int32($)
+{
+    my ($x) = @_;
+    my $r = 1 << 31;
+    return ($x + $r) % (2 * $r) - $r;
+}
+
+sub asm_insn_risuop($)
+{
+    my ($op) = @_;
+    asm_insn_ud1(reg => REG_RAX, reg2 => $op);
+}
+
+sub asm_insn_movT(%)
+{
+    my (%args) = @_;
+
+    if ($is_x86_64) {
+        asm_insn_mov64(%args);
+    } else {
+        asm_insn_mov(%args);
+    }
+}
+
+sub asm_insn_movT_imm(%)
+{
+    my (%args) = @_;
+    my $imm = $args{imm}; delete $args{imm};
+
+    my $is_sint32 = (-0x80000000 <= $imm && $imm <= 0x7fffffff);
+    my $is_uint32 = (0 <= $imm && $imm <= 0xffffffff);
+
+    $args{$is_sint32 || $is_uint32 ? 'imm32' : 'imm64'} = $imm;
+    asm_insn_movT(%args);
+}
+
+sub asm_insn_addT(%)
+{
+    my (%args) = @_;
+
+    if ($is_x86_64) {
+        asm_insn_add64(%args);
+    } else {
+        asm_insn_add(%args);
+    }
+}
+
+sub asm_insn_negT(%)
+{
+    my (%args) = @_;
+
+    if ($is_x86_64) {
+        asm_insn_neg64(%args);
+    } else {
+        asm_insn_neg(%args);
+    }
+}
+
+sub asm_insn_xchgT(%)
+{
+    my (%args) = @_;
+
+    if ($is_x86_64) {
+        asm_insn_xchg64(%args);
+    } else {
+        asm_insn_xchg(%args);
+    }
+}
+
+sub write_random_regdata()
+{
+    my $reg_cnt = $is_x86_64 ? 16 : 8;
+    my $reg_width = $is_x86_64 ? 64 : 32;
+
+    # initialize flags register
+    asm_insn_xor(reg => REG_RAX, reg2 => REG_RAX);
+    asm_insn_sahf();
+
+    # general purpose registers
+    for (my $reg = 0; $reg < $reg_cnt; $reg++) {
+        if ($reg != REG_RSP) {
+            my $imm = randint(width => $reg_width, signed => 1);
+            asm_insn_movT_imm(reg => $reg, imm => $imm);
+        }
+    }
+}
+
+# At the end of this function, we can emit $datalen data-bytes which
+# will be skipped over at runtime, but whose address will be present
+# in EAX and optionally aligned.
+sub prepare_datablock(%)
+{
+    my (%args) = @_;
+    $args{align} = 0 unless defined $args{align} && $args{align} > 1;
+
+    # First, load current EIP/RIP into EAX/RAX. Easy to do on x86_64
+    # thanks to RIP-relative addressing, but on i386 we need to play
+    # some well-known tricks with the CALL instruction. Then, AND the
+    # EAX/RAX register with correct mask to obtain the aligned
+    # address.
+    my $reg = REG_RAX;
+
+    if ($is_x86_64) {
+        my $disp32 = 5;         # 5-byte JMP
+        $disp32 += 4 + ($args{align} - 1) if $args{align}; # 4-byte AND
+
+        asm_insn_lea64(reg => $reg, disp32 => $disp32);
+        asm_insn_and64(reg2 => $reg, imm8 => ~($args{align} - 1))
+            if $args{align};
+    } else {
+        my $imm8 = 1 + 3 + 5;   # 1-byte POP + 3-byte ADD + 5-byte JMP
+        $imm8 += 3 + ($args{align} - 1) if $args{align}; # 3-byte AND
+
+        # displacement = next instruction
+        asm_insn_call(imm32 => 0x00000000);
+        asm_insn_pop(reg => $reg);
+        asm_insn_add(reg2 => $reg, imm8 => $imm8);
+        asm_insn_and(reg2 => $reg, imm8 => ~($args{align} - 1))
+            if $args{align};
+    }
+
+    # JMP over the data blob.
+    asm_insn_jmp(imm32 => $args{datalen});
+}
+
+# Write a block of random data, $datalen bytes long, optionally
+# aligned, and load its address into EAX/RAX.
+sub write_random_datablock(%)
+{
+    my (%args) = @_;
+    prepare_datablock(%args);
+
+    # Generate the random data
+    my $datalen = $args{datalen};
+    for (my $w = 8; 0 < $w; $w /= 2) {
+        for (; $w <= $datalen; $datalen -= $w) {
+            my $value = randint(width => 8 * $w);
+            insnv(value => $value, width => 8 * $w);
+        }
+    }
+}
+
+sub write_random_vregdata(%)
+{
+    my (%args) = @_;
+    $args{ymm} = 0          unless defined $args{ymm};
+    $args{xmm} = $args{ymm} unless defined $args{xmm};
+    $args{mm}  = 0          unless defined $args{mm};
+
+    die "cannot initialize YMM registers only\n"
+        if $args{ymm} && !$args{xmm};
+
+    my $datalen = 0;
+
+    my $mmreg_count = 8;
+    my $mmreg_size  = 8;
+    $datalen += $mmreg_count * $mmreg_size if $args{mm};
+
+    my $xmmreg_count = $is_x86_64 ? 16 : 8;
+    my $xmmreg_size  = 16;
+    $datalen += $xmmreg_count * $xmmreg_size if $args{xmm};
+
+    my $ymmreg_count = $xmmreg_count;
+    my $ymmreg_size  = 32 - $xmmreg_size;
+    $datalen += $ymmreg_count * $ymmreg_size if $args{ymm};
+
+    return unless $datalen > 0;
+
+    # Generate random data blob
+    write_random_datablock(datalen => $datalen + MAXALIGN - 1,
+                           align => MAXALIGN);
+
+    # Load the random data into vector regs.
+    my $offset = 0;
+
+    if ($args{mm}) {
+        for (my $mmreg = 0; $mmreg < $mmreg_count; $mmreg += 1) {
+            asm_insn_movq(reg => $mmreg,
+                          base => REG_RAX,
+                          disp32 => $offset);
+            $offset += $mmreg_size;
+        }
+    }
+    if ($args{ymm}) {
+        for (my $ymmreg = 0; $ymmreg < $ymmreg_count; $ymmreg += 1) {
+            asm_insn_vmovaps(l => ($xmmreg_size + $ymmreg_size) * 8,
+                             reg => $ymmreg,
+                             base => REG_RAX,
+                             disp32 => $offset);
+            $offset += $xmmreg_size + $ymmreg_size;
+        }
+    } elsif ($args{xmm}) {
+        for (my $xmmreg = 0; $xmmreg < $xmmreg_count; $xmmreg += 1) {
+            asm_insn_movaps(reg => $xmmreg,
+                            base => REG_RAX,
+                            disp32 => $offset);
+            $offset += $xmmreg_size;
+        }
+    }
+}
+
+sub write_memblock_setup()
+{
+    # Generate random data blob
+    write_random_datablock(datalen => MEMBLOCK_LEN + MAXALIGN - 1,
+                           align => MAXALIGN);
+
+    # Pointer is in EAX/RAX; set the memblock
+    asm_insn_risuop(RISUOP_SETMEMBLOCK);
+}
+
+sub write_random_register_data(%)
+{
+    my (%args) = @_;
+    write_random_vregdata(%{$args{vregs}}) if defined $args{vregs};
+    write_random_regdata();
+    asm_insn_risuop(RISUOP_COMPARE);
+}
+
+sub write_mem_getoffset(%)
+{
+    my (%args) = @_;
+
+    my @tokens;
+    push @tokens, "BASE"   if defined $args{base};
+    push @tokens, "INDEX"  if defined $args{index};
+    push @tokens, "VINDEX" if defined $args{vindex};
+    push @tokens, "END";
+
+    # (BASE (INDEX | VINDEX)?)? END
+    my $token = shift @tokens;
+
+    if ($token eq "BASE") {
+        $token = shift @tokens;
+        # We must not modify RSP during tests, therefore it cannot be a
+        # base register.
+        return 0 if $args{base} == REG_RSP;
+
+        if ($token eq "VINDEX") {
+            $token = shift @tokens;
+
+            die "VSIB requested, but addrw undefined"
+                unless defined $args{addrw};
+            die "VSIB requested, but count undefined"
+                unless defined $args{count};
+
+            write_mem_getoffset_base_vindex(%args);
+        } elsif ($token eq "INDEX") {
+            $token = shift @tokens;
+            # RSP cannot be used as an index in regular SIB... And we may
+            # not modify it anyway.
+            return 0 if $args{index} == REG_RSP;
+            # If index and base registers are the same, we may not be able
+            # to honor the alignment requirements.
+            return 0 if $args{index} == $args{base};
+
+            write_mem_getoffset_base_index(%args);
+        } else {
+            write_mem_getoffset_base(%args);
+        }
+    }
+
+    die "unexpected junk at the end of getoffset tokens: $token @tokens\n"
+        unless $token eq "END";
+}
+
+sub write_mem_getoffset_base(%)
+{
+    my (%args) = @_;
+
+    if ($args{mask}) {
+        die "size $args{size} is too large for masking"
+            unless $args{size} <= 8;
+        die "simultaneous alignment and masking not supported"
+            if $args{align} > 1;
+
+        prepare_datablock(datalen => $args{size});
+
+        my $width = $args{size} * 8;
+        my $value = randint(width => $width);
+        $value = ($value & ~$args{mask}) | ($args{value} & $args{mask});
+        insnv(value => $value, width => $width, bigendian => 0);
+
+        my $offset = -$args{disp};
+        $offset = wrap_int32($offset) if !$is_x86_64;
+
+        asm_insn_movT_imm(reg => REG_RDX, imm => $offset);
+        asm_insn_addT(reg2 => REG_RAX, reg => REG_RDX);
+    } else {
+        my $offset = int(rand(MEMBLOCK_LEN - $args{size}));
+        $offset &= ~($args{align} - 1);
+
+        $offset -= $args{disp};
+        $offset = wrap_int32($offset) if !$is_x86_64;
+
+        asm_insn_movT_imm(reg => REG_RAX, imm => $offset);
+        asm_insn_risuop(RISUOP_GETMEMBLOCK);
+    }
+
+    asm_insn_xchgT(reg => $args{base}, reg2 => REG_RAX)
+        unless $args{base} == REG_RAX;
+}
+
+sub write_mem_getoffset_base_index(%)
+{
+    my (%args) = @_;
+
+    my $addrw = ($is_x86_64 ? 64 : 32) - $args{ss} - 1;
+    my $index = randint(width => $addrw, signed => 1);
+    $args{disp} += $index * (1 << $args{ss});
+
+    write_mem_getoffset_base(%args);
+    asm_insn_movT_imm(reg => $args{index}, imm => $index);
+}
+
+sub write_mem_getoffset_base_vindex(%)
+{
+    my (%args) = @_;
+
+    my $addrw = $args{addrw} - $args{ss} - 1;
+    my $base = randint(width => $addrw, signed => 1);
+    $args{disp} += $base * (1 << $args{ss});
+
+    my $datalen = $args{addrw} * $args{count} / 8;
+    prepare_datablock(datalen => $datalen);
+
+    for(my $i = 0; $i < $args{count}; ++$i) {
+        my $index = int(rand(MEMBLOCK_LEN - $args{size}));
+        $index &= ~($args{align} - 1);
+        $index >>= $args{ss};
+
+        insnv(value => $base + $index,
+              width => $args{addrw},
+              bigendian => 0);
+    }
+
+    asm_insn_vmovdqu(l => $args{addrw} * $args{count},
+                     reg => $args{vindex},
+                     base => REG_RAX);
+
+    write_mem_getoffset_base(%args, size => MEMBLOCK_LEN);
+}
+
+sub write_mem_getoffset_rollback(%)
+{
+    my (%args) = @_;
+
+    # The base register contains an address of the form &memblock +
+    # offset. We need to turn it into just offset, otherwise we may
+    # get value mismatches since the memory layout can be different.
+    asm_insn_xchgT(reg => $args{base}, reg2 => REG_RAX)
+        unless $args{base} == REG_RAX;
+    asm_insn_negT(reg2 => REG_RAX);
+    asm_insn_risuop(RISUOP_GETMEMBLOCK);
+
+    # I didn't originally think this was neccessary, but there were
+    # random sign-flag mismatch failures on 32-bit, probably due to
+    # the absolute address being randomly in the positive/negative
+    # range of int32 -- the first NEG would then pollute the EFLAGS
+    # register with this information. Using another NEG is a neat
+    # way of overwriting all this information with consistent values.
+    asm_insn_negT(reg2 => REG_RAX);
+}
+
+sub gen_one_insn($)
+{
+    # Given an instruction-details array, generate an instruction
+    my ($rec) = @_;
+    my $insnname = $rec->{name};
+    my $insnwidth = $rec->{width};
+
+    my $constraintfailures = 0;
+
+    my %insn;
+    my %memopts;
+    INSN: while(1) {
+        my $opcode = randint(width => 32);
+        $opcode &= ~$rec->{fixedbitmask};
+        $opcode |= $rec->{fixedbits};
+
+        # This is not 100 % correct, since $opcode is still padded to
+        # 32-bit width. This is necessary so that extract_fields in
+        # eval_constraints_block and eval_memory_block works
+        # correctly, but we need to fix it up before calling asm_insn.
+        %insn                = ();
+        $insn{opcode}{value} = $opcode;
+        $insn{opcode}{width} = $insnwidth;
+
+        my $v = eval_constraints_block(rec => $rec, insn => \%insn,
+                                       is_x86_64 => $is_x86_64);
+        if ($v && !$is_x86_64 && defined $insn{rex}) {
+            # REX.W is part of the opcode; we will never be able to
+            # generate this instruction in 32-bit mode.
+            return 0 if defined $insn{rex}{w} && $insn{rex}{w};
+            $v = 0;
+        }
+        if ($v) {
+            %memopts = eval_memory_block(rec => $rec, insn => \%insn);
+            $v = write_mem_getoffset(%memopts);
+        }
+        if (!$v) {
+            $constraintfailures++;
+            if ($constraintfailures > 10000) {
+                print "10000 consecutive constraint failures for $insnname 
constraints\n";
+                exit (1);
+            }
+            next INSN;
+        }
+
+        # OK, we got a good one
+        $constraintfailures = 0;
+
+        # Get rid of the extra padding before calling asm_insn; see
+        # above for details.
+        $insn{opcode}{value} >>= 32 - $insnwidth;
+
+        asm_insn(%insn);
+        write_mem_getoffset_rollback(%memopts) if $memopts{rollback};
+        asm_insn_risuop(RISUOP_COMPAREMEM)     if $memopts{is_write};
+        asm_insn_risuop(RISUOP_COMPARE);
+
+        return 1;
+    }
+}
+
+sub write_test_code($)
+{
+    my ($params) = @_;
+
+    my $numinsns = $params->{ 'numinsns' };
+    my $outfile = $params->{ 'outfile' };
+
+    my %insn_details = %{ $params->{ 'details' } };
+    my @keys = @{ $params->{ 'keys' } };
+
+    $is_x86_64 = $params->{ 'x86_64' };
+    my $xfeatures = $params->{ 'xfeatures' };
+
+    my %vregs   = ();
+    $vregs{ymm} = $xfeatures eq 'avx';
+    $vregs{xmm} = $vregs{ymm} || $xfeatures eq 'sse';
+    $vregs{mm}  = $vregs{xmm} || $xfeatures eq 'mmx';
+
+    open_bin($outfile);
+
+    # TODO better random number generator?
+    srand(0);
+
+    print "Generating code using patterns: @keys...\n";
+    progress_start(78, $numinsns);
+
+    write_memblock_setup();
+
+    # memblock setup doesn't clean its registers, so this must come afterwards.
+    write_random_register_data(vregs => \%vregs);
+
+    for (my $i = 0; $i < $numinsns;) {
+        my $insn_enc = $keys[int rand (@keys)];
+
+        next if !gen_one_insn($insn_details{$insn_enc});
+        $i += 1;
+
+        # Rewrite the registers periodically. This avoids the tendency
+        # for the VFP registers to decay to NaNs and zeroes.
+        if ($periodic_reg_random && ($i % 100) == 0) {
+            write_random_register_data(vregs => \%vregs);
+        }
+        progress_update($i);
+    }
+    asm_insn_risuop(RISUOP_TESTEND);
+    progress_end();
+    close_bin();
+}
+
+1;
-- 
2.20.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]