[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
- [Qemu-devel] [RISU PATCH v3 09/18] risugen: add --xfeatures option for x86, (continued)
- [Qemu-devel] [RISU PATCH v3 09/18] risugen: add --xfeatures option for x86, Jan Bobek, 2019/07/11
- [Qemu-devel] [RISU PATCH v3 13/18] x86.risu: add SSE3 instructions, Jan Bobek, 2019/07/11
- [Qemu-devel] [RISU PATCH v3 16/18] x86.risu: add AES and PCLMULQDQ instructions, Jan Bobek, 2019/07/11
- [Qemu-devel] [RISU PATCH v3 08/18] risugen: add command-line flag --x86_64, Jan Bobek, 2019/07/11
- [Qemu-devel] [RISU PATCH v3 10/18] x86.risu: add MMX instructions, Jan Bobek, 2019/07/11
- [Qemu-devel] [RISU PATCH v3 06/18] risugen_x86: add module,
Jan Bobek <=
- [Qemu-devel] [RISU PATCH v3 05/18] risugen_x86_memory: add module, Jan Bobek, 2019/07/11
- [Qemu-devel] [RISU PATCH v3 11/18] x86.risu: add SSE instructions, Jan Bobek, 2019/07/11
- [Qemu-devel] [RISU PATCH v3 01/18] risugen_common: add helper functions insnv, randint, Jan Bobek, 2019/07/11