qemu-devel
[Top][All Lists]
Advanced

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

[Qemu-devel] [RISU RFC PATCH v1 4/7] risugen_x86: add module


From: Jan Bobek
Subject: [Qemu-devel] [RISU RFC PATCH v1 4/7] risugen_x86: add module
Date: Wed, 19 Jun 2019 01:04:44 -0400

The risugen_x86.pm module contains most of the code specific to Intel
i386 and x86_64 architectures. This commit also adds --x86_64 option,
which enables emission of 64-bit (rather than 32-bit) assembly.

Signed-off-by: Jan Bobek <address@hidden>
---
 risugen        |   6 +-
 risugen_x86.pm | 455 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 460 insertions(+), 1 deletion(-)
 create mode 100644 risugen_x86.pm

diff --git a/risugen b/risugen
index fe3d00e..09a702a 100755
--- a/risugen
+++ b/risugen
@@ -310,6 +310,7 @@ Valid options:
                    Useful to test before support for FP is available.
     --sve        : enable sve floating point
     --be         : generate instructions in Big-Endian byte order (ppc64 only).
+    --x86_64     : generate 64-bit (rather than 32-bit) x86 code.
     --help       : print this message
 EOT
 }
@@ -322,6 +323,7 @@ sub main()
     my $fp_enabled = 1;
     my $sve_enabled = 0;
     my $big_endian = 0;
+    my $is_x86_64 = 0;
     my ($infile, $outfile);
 
     GetOptions( "help" => sub { usage(); exit(0); },
@@ -338,6 +340,7 @@ sub main()
                 },
                 "be" => sub { $big_endian = 1; },
                 "no-fp" => sub { $fp_enabled = 0; },
+                "x86_64" => sub { $is_x86_64 = 1; },
                 "sve" => sub { $sve_enabled = 1; },
         ) or return 1;
     # allow "--pattern re,re" and "--pattern re --pattern re"
@@ -372,7 +375,8 @@ sub main()
         'keys' => \@insn_keys,
         'arch' => $full_arch[0],
         'subarch' => $full_arch[1] || '',
-        'bigendian' => $big_endian
+        'bigendian' => $big_endian,
+        'x86_64' => $is_x86_64
     );
 
     write_test_code(\%params);
diff --git a/risugen_x86.pm b/risugen_x86.pm
new file mode 100644
index 0000000..879d6e1
--- /dev/null
+++ b/risugen_x86.pm
@@ -0,0 +1,455 @@
+#!/usr/bin/perl -w
+###############################################################################
+# Copyright (c) 2019 Linaro Limited
+# 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_emit;
+
+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 write_risuop($)
+{
+    my ($op) = @_;
+
+    write_insn(opcode => X86OP_UD1,
+               modrm => {mod => MOD_DIRECT,
+                         reg => REG_EAX,
+                         rm => $op});
+}
+
+sub write_mov_rr($$)
+{
+    my ($r1, $r2) = @_;
+
+    my %insn = (opcode => X86OP_MOV,
+                modrm => {mod => MOD_DIRECT,
+                          reg => ($r1 & 0x7),
+                          rm => ($r2 & 0x7)});
+
+    $insn{rex}{w} = 1 if $is_x86_64;
+    $insn{rex}{r} = 1 if $r1 >= 8;
+    $insn{rex}{b} = 1 if $r2 >= 8;
+
+    write_insn(%insn);
+}
+
+sub write_mov_reg_imm($$)
+{
+    my ($reg, $imm) = @_;
+
+    my %insn = (opcode => {value => 0xB8 | ($reg & 0x7), len => 1},
+                imm => {value => $imm, len => $is_x86_64 ? 8 : 4});
+
+    $insn{rex}{w} = 1 if $is_x86_64;
+    $insn{rex}{b} = 1 if $reg >= 8;
+
+    write_insn(%insn);
+}
+
+sub write_random_regdata()
+{
+    my $reg_cnt = $is_x86_64 ? 16 : 8;
+    my $bitlen = $is_x86_64 ? 64 : 32;
+
+    # initialize flags register
+    write_insn(opcode => X86OP_XOR,
+               modrm => {mod => MOD_DIRECT,
+                         reg => REG_EAX,
+                         rm => REG_EAX});
+    write_insn(opcode => X86OP_SAHF);
+
+    # general purpose registers
+    for (my $reg = 0; $reg < $reg_cnt; $reg++) {
+        if ($reg != REG_ESP) {
+            my $imm = randint_constr(bitlen => $bitlen, signed => 1);
+            write_mov_reg_imm($reg, $imm);
+        }
+    }
+}
+
+sub write_random_datablock($)
+{
+    my ($datalen) = @_;
+
+    # Write a block of random data, $datalen bytes long, aligned
+    # according to MAXALIGN, and load its address into EAX/RAX.
+
+    $datalen += MAXALIGN - 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 CALL instruction.
+    if ($is_x86_64) {
+        # 4-byte AND + 5-byte JMP
+        my $disp32 = 4 + 5 + (MAXALIGN - 1);
+        my $reg = REG_EAX;
+
+        write_insn(rex => {w => 1},
+                   opcode => X86OP_LEA,
+                   modrm => {mod => MOD_INDIRECT,
+                             reg => $reg, rm => REG_EBP},
+                   disp => {value => $disp32, len => 4});
+
+        write_insn(rex => {w => 1},
+                   opcode => X86OP_ALU_imm8,
+                   modrm => {mod => MOD_DIRECT,
+                             reg => 4, rm => $reg},
+                   imm => {value => ~(MAXALIGN - 1),
+                           len => 1});
+
+    } else {
+        # 1-byte POP + 3-byte ADD + 3-byte AND + 5-byte JMP
+        my $imm8 = 1 + 3 + 3 + 5 + (MAXALIGN - 1);
+        my $reg = REG_EAX;
+
+        # displacement = next instruction
+        write_insn(opcode => X86OP_CALL,
+                   imm => {value => 0x00000000, len => 4});
+
+        write_insn(opcode => {value => 0x58 | ($reg & 0x7),
+                              len => 1});
+
+        write_insn(opcode => X86OP_ALU_imm8,
+                   modrm => {mod => MOD_DIRECT,
+                             reg => 0, rm => $reg},
+                   imm => {value => $imm8, len => 1});
+
+        write_insn(opcode => X86OP_ALU_imm8,
+                   modrm => {mod => MOD_DIRECT,
+                             reg => 4, rm => $reg},
+                   imm => {value => ~(MAXALIGN - 1),
+                           len => 1});
+    }
+
+    # JMP over the data blob.
+    write_insn(opcode => X86OP_JMP,
+               imm => {value => $datalen, len => 4});
+
+    # Generate the random data
+    for (my $w = 8; 0 < $w; $w /= 2) {
+        for (; $w <= $datalen; $datalen -= $w) {
+            insnv(%{rand_insn_imm(size => $w)});
+        }
+    }
+}
+
+sub write_random_xmmdata()
+{
+    my $xmm_cnt = $is_x86_64 ? 16 : 8;
+    my $xmm_len = 16;
+    my $datalen = $xmm_cnt * $xmm_len;
+
+    # Generate random data blob
+    write_random_datablock($datalen);
+
+    # Load the random data into XMM regs.
+    for (my $xmm_reg = 0; $xmm_reg < $xmm_cnt; $xmm_reg++) {
+        my %insn = (opcode => X86OP_MOVAPS,
+                    modrm => {mod => MOD_INDIRECT_DISP32,
+                              reg => ($xmm_reg & 0x7),
+                              rm => REG_EAX},
+                    disp => {value => $xmm_reg * $xmm_len,
+                             len => 4});
+
+        $insn{rex}{r} = 1 if $xmm_reg >= 8;
+
+        write_insn(%insn);
+    }
+}
+
+sub write_memblock_setup()
+{
+    # Generate random data blob
+    write_random_datablock(MEMBLOCK_LEN);
+    # Pointer is in EAX/RAX; set the memblock
+    write_risuop(RISUOP_SETMEMBLOCK);
+}
+
+sub write_random_register_data()
+{
+    write_random_xmmdata();
+    write_random_regdata();
+    write_risuop(RISUOP_COMPARE);
+}
+
+sub rand_insn_imm(%)
+{
+    my (%args) = @_;
+
+    return {
+        value => randint_constr(bitlen => ($args{size} * 8), signed => 1),
+        len => $args{size}
+    };
+}
+
+sub rand_insn_opcode($)
+{
+    # Given an instruction-details array, generate an instruction
+    my ($rec) = @_;
+    my $insnname = $rec->{name};
+    my $insnwidth = $rec->{width};
+
+    my $constraintfailures = 0;
+
+    INSN: while(1) {
+        my $opcode = randint_constr(bitlen => 32,
+                                    fixedbits => $rec->{fixedbits},
+                                    fixedbitmask => $rec->{fixedbitmask});
+
+        my $constraint = $rec->{blocks}{"constraints"};
+        if (defined $constraint) {
+            # user-specified constraint: evaluate in an environment
+            # with variables set corresponding to the variable fields.
+            my $v = eval_with_fields($insnname, $opcode, $rec, "constraints", 
$constraint);
+            if (!$v) {
+                $constraintfailures++;
+                if ($constraintfailures > 10000) {
+                    print "10000 consecutive constraint failures for $insnname 
constraints string:\n$constraint\n";
+                    exit (1);
+                }
+                next INSN;
+            }
+        }
+
+        # OK, we got a good one
+        $constraintfailures = 0;
+
+        return {
+            value => $opcode >> (32 - $insnwidth),
+            len => $insnwidth / 8
+        };
+    }
+}
+
+sub rand_insn_modrm($$)
+{
+    my ($opts, $insn) = @_;
+    my $modrm;
+
+    while (1) {
+        $modrm = rand_fill({mod => {bitlen => 2},
+                            reg => {bitlen => 3},
+                            rm => {bitlen => 3}},
+                           $opts);
+
+        if ($modrm->{mod} != MOD_DIRECT) {
+            # Displacement only; we cannot use this since we
+            # don't know absolute address of the memblock.
+            next if $modrm->{mod} == MOD_INDIRECT && $modrm->{rm} == REG_EBP;
+
+            if ($modrm->{rm} == REG_ESP) {
+                # SIB byte present
+                my $sib = rand_fill({ss => {bitlen => 2},
+                                     index => {bitlen => 3},
+                                     base => {bitlen => 3}}, {});
+
+                # We cannot modify ESP/RSP during the tests
+                next if $sib->{base} == REG_ESP;
+
+                # When base and index register are the same,
+                # computing the correct memblock addresses and
+                # offsets gets way too complicated...
+                next if $sib->{base} == $sib->{index};
+
+                # No base register
+                next if $modrm->{mod} == MOD_INDIRECT && $sib->{base} == 
REG_EBP;
+
+                $insn->{sib} = $sib;
+            }
+
+            $insn->{disp} = rand_insn_imm(size => 1)
+                if $modrm->{mod} == MOD_INDIRECT_DISP8;
+
+            $insn->{disp} = rand_insn_imm(size => 4)
+                if $modrm->{mod} == MOD_INDIRECT_DISP32;
+        }
+
+        $insn->{modrm} = $modrm;
+        last;
+    }
+}
+
+sub rand_insn_rex($$)
+{
+    my ($opts, $insn) = @_;
+
+    $opts->{w} = 0 unless defined $opts->{w};
+    $opts->{x} = 0 unless defined $opts->{x} || defined $insn->{sib};
+
+    my $rex = rand_fill({w => {bitlen => 1},
+                         r => {bitlen => 1},
+                         b => {bitlen => 1},
+                         x => {bitlen => 1}},
+                        $opts);
+
+    $insn->{rex} = $rex
+        if $rex->{w} || $rex->{r} || $rex->{b} || $rex->{x};
+}
+
+sub write_mem_getoffset($$)
+{
+    my ($opts, $insn) = @_;
+    my $offset, my $index;
+
+    $opts->{size}  = 0 unless defined $opts->{size};
+    $opts->{align} = 1 unless defined $opts->{align};
+
+    if (!defined $opts->{base}
+        && defined $insn->{modrm}
+        && $insn->{modrm}{mod} != MOD_DIRECT) {
+
+        $opts->{base} = (defined $insn->{sib}
+                         ? $insn->{sib}{base}
+                         : $insn->{modrm}{rm});
+
+        if ($insn->{modrm}{mod} == MOD_INDIRECT && $opts->{base} == REG_EBP) {
+            delete $opts->{base}; # No base register
+        } else {
+            $opts->{base} |= $insn->{rex}{b} << 3 if defined $insn->{rex};
+            $opts->{base} |= (!$insn->{vex}{b}) << 3 if defined $insn->{vex};
+        }
+    }
+
+    if (!defined $opts->{index} && defined $insn->{sib}) {
+        $opts->{index} = $insn->{sib}{index};
+        $opts->{index} |= $insn->{rex}{x} << 3 if defined $insn->{rex};
+        $opts->{index} |= (!$insn->{vex}{x}) << 3 if defined $insn->{vex};
+        delete $opts->{index} if $opts->{index} == REG_ESP; # ESP means "none"
+    }
+
+    $opts->{ss} = $insn->{sib}{ss} if !defined $opts->{ss} && defined 
$insn->{sib};
+    $opts->{disp} = $insn->{disp} if !defined $opts->{disp} && defined 
$insn->{disp};
+
+    $offset = int(rand(MEMBLOCK_LEN - $opts->{size}));
+    $offset &= ~($opts->{align} - 1);
+
+    $offset -= $opts->{disp}{value} if defined $opts->{disp};
+
+    if (defined $opts->{index}) {
+        $index = randint_constr(bitlen => 32, signed => 1);
+        $offset -= $index * (1 << $opts->{ss});
+    }
+
+    if (defined $opts->{base} && defined $offset) {
+        write_mov_reg_imm(REG_EAX, $offset);
+        write_risuop(RISUOP_GETMEMBLOCK);
+        write_mov_rr($opts->{base}, REG_EAX);
+    }
+    if (defined $opts->{index} && defined $index) {
+        write_mov_reg_imm($opts->{index}, $index);
+    }
+}
+
+sub gen_one_insn($)
+{
+    my ($rec) = @_;
+    my $insn;
+
+    $insn->{opcode} = rand_insn_opcode($rec);
+    my $opts = parse_emitblock($rec, $insn);
+
+    # Operation with a ModR/M byte can potentially use a memory
+    # operand
+    $opts->{mem} = {}
+        unless defined $opts->{mem} || !defined $opts->{modrm};
+
+    # If none of REX/VEX/EVEX are specified, default to REX
+    $opts->{rex} = {}
+        unless defined $opts->{rex} || defined $opts->{vex} || defined 
$opts->{evex};
+
+    # REX requires x86_64
+    delete $opts->{rex}
+        unless $is_x86_64;
+
+    $insn->{rep}    = $opts->{rep}    if defined $opts->{rep};
+    $insn->{repne}  = $opts->{repne}  if defined $opts->{repne};
+    $insn->{data16} = $opts->{data16} if defined $opts->{data16};
+
+    rand_insn_modrm($opts->{modrm}, $insn) if defined $opts->{modrm};
+
+    # TODO rand_insn_vex($opts->{vex}, $insn) if defined $opts->{vex};
+    # TODO rand_insn_evex($opts->{evex}, $insn) if defined $opts->{evex};
+    rand_insn_rex($opts->{rex}, $insn) if defined $opts->{rex};
+
+    $insn->{imm} = rand_insn_imm(%{$opts->{imm}}) if defined $opts->{imm};
+
+    write_mem_getoffset($opts->{mem}, $insn);
+    write_insn(%{$insn});
+}
+
+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' };
+
+    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();
+
+    for my $i (1..$numinsns) {
+        my $insn_enc = $keys[int rand (@keys)];
+        # my $forcecond = (rand() < $condprob) ? 1 : 0;
+        gen_one_insn($insn_details{$insn_enc});
+        write_risuop(RISUOP_COMPARE);
+        # 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();
+        }
+        progress_update($i);
+    }
+    write_risuop(RISUOP_TESTEND);
+    progress_end();
+    close_bin();
+}
+
+1;
-- 
2.20.1




reply via email to

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