From: Florian K. <fk...@so...> - 2025-03-06 17:43:45
|
https://sourceware.org/git/gitweb.cgi?p=valgrind.git;h=b98855efc89a402357469fbde06912a3bc0b0bfd commit b98855efc89a402357469fbde06912a3bc0b0bfd Author: Florian Krohm <fl...@ei...> Date: Thu Mar 6 17:42:05 2025 +0000 s390x: Add disassembly checker (Bug 498037) Add program disasm-test to check that s390_disasm generates the same disassembly for a given insn than objdump -d does. The focus is on insns that have extended mnemonics most of which are vector insns. The checker resides in none/tests/s390x/disasm-test with comprehensive documentation in the README file there. It is integrated into the regression testing framework but currently disabled, because s390_disasm has not been fixed yet. Fixes https://bugs.kde.org/show_bug.cgi?id=498037 Diff: --- .gitignore | 11 + Makefile.am | 1 + NEWS | 1 + configure.ac | 1 + none/tests/s390x/disasm-test/Makefile.am | 35 + none/tests/s390x/disasm-test/README | 210 +++++ .../tests/s390x/disasm-test/disasm-test.stderr.exp | 3 + .../tests/s390x/disasm-test/disasm-test.stdout.exp | 0 none/tests/s390x/disasm-test/disasm-test.vgtest | 6 + none/tests/s390x/disasm-test/filter_stderr | 3 + none/tests/s390x/disasm-test/generate.c | 596 +++++++++++++ none/tests/s390x/disasm-test/main.c | 371 ++++++++ none/tests/s390x/disasm-test/main.h | 101 +++ none/tests/s390x/disasm-test/objdump.c | 237 +++++ none/tests/s390x/disasm-test/objdump.h | 55 ++ none/tests/s390x/disasm-test/opcode.c | 964 +++++++++++++++++++++ none/tests/s390x/disasm-test/verify.c | 149 ++++ none/tests/s390x/disasm-test/vex.c | 145 ++++ none/tests/s390x/disasm-test/vex.h | 32 + 19 files changed, 2921 insertions(+) diff --git a/.gitignore b/.gitignore index de2306df78..c394d27175 100644 --- a/.gitignore +++ b/.gitignore @@ -2231,6 +2231,17 @@ /none/tests/riscv64/integer /none/tests/riscv64/muldiv +# /none/tests/s390x/disasm-test/ +/none/tests/s390x/disasm-test/*.dSYM +/none/tests/s390x/disasm-test/*.stderr.diff* +/none/tests/s390x/disasm-test/*.stderr.out +/none/tests/s390x/disasm-test/*.stdout.diff* +/none/tests/s390x/disasm-test/*.stdout.out +/none/tests/s390x/disasm-test/.deps +/none/tests/s390x/disasm-test/Makefile +/none/tests/s390x/disasm-test/Makefile.in +/none/tests/s390x/disasm-test/disasm-test + # /none/tests/scripts/ /none/tests/scripts/*.dSYM /none/tests/scripts/*.so diff --git a/Makefile.am b/Makefile.am index db8cfa382c..14309dacfa 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,6 +33,7 @@ SUBDIRS = \ perf \ gdbserver_tests \ memcheck/tests/vbit-test \ + none/tests/s390x/disasm-test \ auxprogs \ mpi \ solaris \ diff --git a/NEWS b/NEWS index ad58e72152..fd470fee78 100644 --- a/NEWS +++ b/NEWS @@ -48,6 +48,7 @@ are not entered into bugzilla tend to get forgotten about or ignored. 497455 Update drd/scripts/download-and-build-gcc 497723 Enabling Ada demangling breaks callgrind differentiation between overloaded functions and procedures +498037 s390x: Add disassembly checker 498143 False positive on EVIOCGRAB ioctl 498317 FdBadUse is not a valid CoreError type in a suppression even though it's generated by --gen-suppressions=yes diff --git a/configure.ac b/configure.ac index e6ae0501bd..5d3c6d02d4 100755 --- a/configure.ac +++ b/configure.ac @@ -5722,6 +5722,7 @@ AC_CONFIG_FILES([ none/tests/arm/Makefile none/tests/arm64/Makefile none/tests/s390x/Makefile + none/tests/s390x/disasm-test/Makefile none/tests/mips32/Makefile none/tests/mips64/Makefile none/tests/nanomips/Makefile diff --git a/none/tests/s390x/disasm-test/Makefile.am b/none/tests/s390x/disasm-test/Makefile.am new file mode 100644 index 0000000000..7758489219 --- /dev/null +++ b/none/tests/s390x/disasm-test/Makefile.am @@ -0,0 +1,35 @@ +include $(top_srcdir)/Makefile.all.am + +EXTRA_DIST = disasm-test.vgtest disasm-test.stderr.exp disasm-test.stdout.exp + +dist_noinst_SCRIPTS = filter_stderr + +#---------------------------------------------------------------------------- +# Headers +#---------------------------------------------------------------------------- + +pkginclude_HEADERS = +noinst_HEADERS = main.h objdump.h vex.h + +#---------------------------------------------------------------------------- +# disasm_test +#---------------------------------------------------------------------------- + +noinst_PROGRAMS = disasm-test + +SOURCES = \ + main.c \ + generate.c \ + objdump.c \ + opcode.c \ + verify.c \ + vex.c + +disasm_test_SOURCES = $(SOURCES) +disasm_test_CPPFLAGS = $(AM_CPPFLAGS_PRI) \ + -I$(top_srcdir)/VEX/pub \ + -I$(top_srcdir)/VEX/priv +disasm_test_CFLAGS = $(AM_CFLAGS_PRI) +disasm_test_DEPENDENCIES = +disasm_test_LDADD = $(top_builddir)/VEX/libvex-@VGCONF_ARCH_PRI@-@VGCONF_OS@.a +disasm_test_LDFLAGS = $(AM_CFLAGS_PRI) @LIB_UBSAN@ diff --git a/none/tests/s390x/disasm-test/README b/none/tests/s390x/disasm-test/README new file mode 100644 index 0000000000..54df05cf98 --- /dev/null +++ b/none/tests/s390x/disasm-test/README @@ -0,0 +1,210 @@ +disasm-test + +The purpose of this program is to ensure that the disassembly as +generated by s390_disasm matches what objdump -d produces for any +given insn. As such the program runs as part of "make regtest". + + +How it works +------------ +1) Given an opcode, generate a C file with testcases. +2) Compile the C file. +3) Run objdump -d on the object file and capture the result in a file. + This file will be referred to as "the objdump file". +4) Read the objdump file, extract the insn bytes and disassembly. +5) Feed the so-extracted insn bytes into VEX's decode-and-irgen + machinery with disassembly (= tracing frontend) being enabled. +6) Intercept the so-disassembled text and compare it with the + disassembly in the objdump file. + + +Invocation +---------- +See disasm-test --help for the most up-to-date instructions. + +disasm-test --all + For all opcodes known to disasm-test, generate testcases and + verify the disassembly. This is how disasm-test is invoked + during regression testing. + +disasm-test --generate OPCODE_NAMEs + For each specified opcode, generate a C file with testcases, + compile it and create the objdump file. + Useful when adding new opcodes. + +disasm-test --verify OBJDUMP_FILEs + For each specified objdump file, verify that the disassembly via VEX + matches. Useful when adding new opcodes. + +disasm-test --run OPCODE_NAMEs + Combines --generate and --verify. Useful when adding new opcodes. + + +Other non-debugging options +--------------------------- +--verbose + Reports activity and miscellaneous information deemed interesting. + +--summary + Write out test generation summary. This option is only observed in + combination with --all. + +--gcc=/path/to/gcc + Specify which GCC to use. Default is: gcc on $PATH. + +--gcc-flags=FLAGS + Specify which flags GCC to use. Default is: "-c -march=arch14". + +--objdump=/path/to/objdump + Specify which objdump to use. Default is: objdump on $PATH. + +--keep-temp + Keep generated files: .c file with testcases, object file, objdump + file, and .vex file + +--show-exc-spec + Show generated insns that cause specification exceptions. + Because insns causing specification exceptions are typically accepted + by GCC and objdump an objdump file may contain them. But comparing + them is pointless. + +--no-show-miscompares + Do not show miscomparing insns. + + +Debugging options +----------------- +--debug + Additional debugging output. + +--d12=INT + Use INT as the value for d12 fields. The given value is checked + for feasibility. + +--d20=INT + Use INT as the value for d20 fields. The given value is checked + for feasibility. + +--sint=INT + Use INT as the only value for signed integer fields. The given value + is NOT checked for feasibility (as the field width is not known). + The value is expected to fit in 32 bits (and is complained about + if it doesn't), as there are no opcodes with immediate operands + that have more than 32 bits. + +--uint=INT + Use INT as the only value for unsigned integer fields. The given value + is NOT checked for feasibility (as the field width is not known). + The value is expected to fit in 32 bits (and is complained about + if it doesn't), as there are no opcodes with immediate operands + that have more than 32 bits. + + +Testcase generation +------------------- +Testcase generation is not exhaustive. That would produce too large a +number of testcases. For example, testing CRB R1,R2,M3,D4(B4) +exhaustively would produce 16x16x16x12x16 = 786432 testcases. Instead, +we attempt to create "interesting" testcases. Like so: + +- for a VR operand pick a VR at random +- for a GPR operand that is not an index or base register pick a GPR + at random +- for a GPR that is base or index register, pick r0 and another GPR + at random +- for a 12-bit displacement pick 0, 1, 2, 4095 +- for a 20-bit displacement pick -524288, -2, -1, 0, 1, 2, 524287 +- for a signed integer field pick the boundary values of its domain and + -2,-1,0,1,2 +- for an unsigned integer field pick the boundary values of its domain + and 1, 2 +- for a mask field, pick all allowed values + +Why are we picking these values? Boundary cases are *always* +interesting, as is 0. '1' is picked because it is odd and '2' is picked +because it is even. + +Note: if an opcode has more than one GPR operand choose different +registers. We do this because we want to catch bugs due to mixed up +registers. +E.g. If the testcase is "brxh r1,r2,8" and s390_disasm produces +"brxh r2,r1,8" we want to catch that. Obviously, to do so the registers +need to be different. The same applies to VRs. + + +Adding a new opcode +------------------- +See extensive documentation at the top of file opcode.c +Any opcode can be added. It is not necessary for the opcode to have +extended mnemonics. + + +Integration into regression testing +----------------------------------- +1) Observe the exit code + + disasm-test --all --no-show-miscompares + + There will be no output to stdout and stderr. If there are no + miscompares in the disassembly the exit code will be 0. Otherwise, + it will be 1. + +2) Observe stderr + + disasm-test --all + + Miscomparing disassembly will be written to stderr. Exit code as + described above. + +3) Observe testcase summary + + disasm-test --all --no-show-miscompares --summary + + Will write information about #testcases as well as failing ones + to stdout. Exit code as described above. + + +Status +------ +Only opcodes with extended mnemonics as identified in Appendix J of the +Principles of Operation are considered. + +There is only partial support for opcodes with optional operands. +In the sense that the generated testcases will always include the +optional operand. + + +TODO +---- +(1) Testcase generation for the "Rotate and ..." family of opcodes needs + to be improved. Several interesting cases involving the T- and Z-bit + are not considered. + +(2) Due to bugs and missing functionality the following miscompares are + observed at this point: + - all vector opcodes (missing functionality) + - c[l][g]rt (bug in s390_disasm) + - bc (bug in objdump 2.38) + +(3) Generated testcases may cause specification exceptions. This + happens iff a constraint involves more than one opcode field. + E.g.: for the VREP opcode the M4 value determines which I2 values + are allowed. This constraint cannot be expressed. However, the + disassembly for such insns is not compared. Use --show-spec-exc + to show those insns. + +(4) In s390_decode_and_irgen the code peeks past the current insn: + + /* If next instruction is execute, stop here */ + if (dis_res->whatNext == Dis_Continue && + (bytes[insn_length] == 0x44 || + (bytes[insn_length] == 0xc6 && (bytes[insn_length + 1] & 0xf) == 0))) { + + Because of this we need to make our insn buffer 7 bytes instead + of 6 and set insn_buf[6] = 0x0. This works because 0x0 != 0x44 + and 0x0 != 0xc6. + Perhaps disable the peek-ahead when inside disasm-test by means + of some global variable? Not pretty either, but explicit. + +(5) For D20XB and D12XB operands add a test with B == X and B != 0 + Not exactly easy to do. diff --git a/none/tests/s390x/disasm-test/disasm-test.stderr.exp b/none/tests/s390x/disasm-test/disasm-test.stderr.exp new file mode 100644 index 0000000000..e6a4c48218 --- /dev/null +++ b/none/tests/s390x/disasm-test/disasm-test.stderr.exp @@ -0,0 +1,3 @@ + +One of --verify, --generate, --run, --all, or --unit-test is required + diff --git a/none/tests/s390x/disasm-test/disasm-test.stdout.exp b/none/tests/s390x/disasm-test/disasm-test.stdout.exp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/none/tests/s390x/disasm-test/disasm-test.vgtest b/none/tests/s390x/disasm-test/disasm-test.vgtest new file mode 100644 index 0000000000..d7a1a409c1 --- /dev/null +++ b/none/tests/s390x/disasm-test/disasm-test.vgtest @@ -0,0 +1,6 @@ +prog: disasm-test +# args: --all # enable this eventually +# +# NOTE: there are extra newlines in the output which are *not* +# present when disasm-test is invoked by hand. +# Not sure where they are coming from. diff --git a/none/tests/s390x/disasm-test/filter_stderr b/none/tests/s390x/disasm-test/filter_stderr new file mode 100755 index 0000000000..f4c67057ee --- /dev/null +++ b/none/tests/s390x/disasm-test/filter_stderr @@ -0,0 +1,3 @@ +#!/bin/sh + +../../filter_stderr "$@" diff --git a/none/tests/s390x/disasm-test/generate.c b/none/tests/s390x/disasm-test/generate.c new file mode 100644 index 0000000000..7e69085e38 --- /dev/null +++ b/none/tests/s390x/disasm-test/generate.c @@ -0,0 +1,596 @@ +/* -*- mode: C; c-basic-offset: 3; -*- */ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2024-2025 Florian Krohm + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + The GNU General Public License is contained in the file COPYING. +*/ + +#include <stdio.h> // fprintf +#include <stdlib.h> // system +#include <string.h> // strlen +#include <stdarg.h> // va_list +#include <ctype.h> // isdigit +#include <assert.h> // assert +#include "main.h" // error +#include "objdump.h" // MARK + +// FIXME: if more than one VR or GPR (non-base, non-index) are used in +// an opcode use different register! So we can recognise a mixup in +// register order. E.g. vctz %v2,%v2,3 will not allow to detect whether +// the two registers was mixed up. +static unsigned num_tests; // # generated tests + +static void run_cmd(const char *, ...); + + +static const char * +gpr_operand(unsigned regno) +{ + static const char *gprs[] = { + "%r0", "%r1", "%r2", "%r3", "%r4", "%r5", "%r6", "%r7", + "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15" + }; + + return gprs[regno]; +} + + +static const char * +vr_operand(unsigned regno) +{ + static const char *vrs[] = { + "%v0", "%v1", "%v2", "%v3", "%v4", "%v5", "%v6", "%v7", + "%v8", "%v9", "%v10", "%v11", "%v12", "%v13", "%v14", "%v15", + "%v16", "%v17", "%v18", "%v19", "%v20", "%v21", "%v22", "%v23", + "%v24", "%v25", "%v26", "%v27", "%v28", "%v29", "%v30", "%v31" + }; + + return vrs[regno]; +} + + +static unsigned +random_gpr(int allow_r0) +{ + unsigned regno; + + if (allow_r0) { + regno = rand() % 16; + } else { + do { + regno = rand() % 16; + } while (regno == 0); + } + + return regno; +} + + +static unsigned +random_vr(void) +{ + return rand() % 32; +} + + +#if 0 +/* These functions are currently unused. But may become useful in + alternate test generation strategies that use random values instead + of hardwired interesting ones. */ + +static unsigned +random_uint(unsigned num_bits) +{ + assert(num_bits <= 32); + + long long val = rand(); + return val % (1LL << num_bits); +} + + +static int +random_sint(unsigned num_bits) +{ + assert(num_bits <= 32); + + static int sign = 1; + + long long val = rand(); // positive value + int value = val % (1LL << (num_bits - 1)); + + /* alternate */ + if (sign == -1) + value = -value; + sign = -sign; + return value; +} + + +static unsigned +d12_value(void) +{ + return d12_val_specified ? d12_val : random_uint(12); +} + + +static int +d20_value(void) +{ + return d20_val_specified ? d20_val : random_sint(20); +} + + +static unsigned +uint_value(unsigned num_bits) +{ + if (num_bits > 32) + fatal("integer operand > 32 bits not supported\n"); + return uint_val_specified ? uint_val : random_uint(num_bits); +} + + +static int +sint_value(unsigned num_bits) +{ + if (num_bits > 32) + fatal("integer operand > 32 bits not supported\n"); + return sint_val_specified ? sint_val : random_sint(num_bits); +} +#endif + +/* MASK is a bitvector. For a GPR rk the k'th bit will be set. The + function returns a register number which has not been used and + adjusts the bitvector. */ +static unsigned +unique_gpr(unsigned regno, unsigned *mask) +{ + assert(regno < 16); + assert(*mask != ~0U); // Paranoia: avoid infinite loop + + unsigned bit = 1 << regno; + while (*mask & bit) { + regno = random_gpr(/* allow_r0 */1); + bit = 1 << regno; + } + *mask |= bit; + return regno; +} + + +/* Like unique_gpr */ +static unsigned +unique_vr(unsigned regno, unsigned *mask) +{ + assert(regno < 32); + assert(*mask != ~0U); // Paranoia: avoid infinite loop + + unsigned bit = 1 << regno; + while (*mask & bit) { + regno = random_vr(); + bit = 1 << regno; + } + *mask |= bit; + return regno; +} + + +/* Field */ +typedef struct { + const opnd *operand; // the operand to which this field belongs + int is_displacement; // only relevant for OPND_D12/20[X]B operands + int is_length; // only relevant for OPND_D12LB operands + int is_vr; // only relevant for OPND_D12VB operands + long long assigned_value; +} field; + + +/* Write out a single ASM statement for OPC. */ +static void +write_asm_stmt(FILE *fp, const opcode *opc, const field fields[]) +{ + fprintf(fp, " asm volatile(\"%s ", opc->name); + + unsigned gpr_mask, vr_mask, regno; + int inc; + int needs_comma = 0; + + gpr_mask = vr_mask = 0; + for (int i = 0; i < opc->num_fields; i += inc) { + const opnd *operand = fields[i].operand; + + inc = 1; // for most operand kinds + + if (needs_comma++) + fputc(',', fp); + switch (operand->kind) { + case OPND_GPR: + regno = unique_gpr(fields[i].assigned_value, &gpr_mask); + fprintf(fp, "%s", gpr_operand(regno)); + break; + case OPND_VR: + regno = unique_vr(fields[i].assigned_value, &vr_mask); + fprintf(fp, "%s", vr_operand(regno)); + break; + case OPND_D12XB: + case OPND_D20XB: { + long long d = fields[i].assigned_value; + const char *x = gpr_operand(fields[i + 1].assigned_value); + const char *b = gpr_operand(fields[i + 2].assigned_value); + fprintf(fp, "%lld(%s,%s)", d, x, b); + inc = 3; + break; + } + case OPND_D12VB: { + long long d = fields[i].assigned_value; + const char *v = vr_operand(fields[i + 1].assigned_value); + const char *b = gpr_operand(fields[i + 2].assigned_value); + fprintf(fp, "%lld(%s,%s)", d, v, b); + inc = 3; + break; + } + case OPND_D12LB: { + long long d = fields[i].assigned_value; + unsigned l = fields[i + 1].assigned_value; + const char *b = gpr_operand(fields[i + 2].assigned_value); + fprintf(fp, "%lld(%u,%s)", d, l + 1, b); + inc = 3; + break; + } + case OPND_D12B: + case OPND_D20B: { + long long d = fields[i].assigned_value; + const char *b = gpr_operand(fields[i + 1].assigned_value); + fprintf(fp, "%lld(%s)", d, b); + inc = 2; + break; + } + case OPND_MASK: + case OPND_SINT: + case OPND_UINT: + fprintf(fp, "%lld", fields[i].assigned_value); + break; + case OPND_PCREL: + fprintf(fp, "%lld*2", fields[i].assigned_value); + break; + default: + assert(0); + } + } + fprintf(fp, "\");\n"); + + ++num_tests; +} + + +/* IX identifies the element of the FIELDS array to which a value + will be assigned in this iteration. */ +static void +iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix) +{ + /* All fields are assigned. Write out the asm stmt */ + if (ix == opc->num_fields) { + write_asm_stmt(fp, opc, fields); + return; + } + + field *f = fields + ix; + const opnd *operand = f->operand; + + switch (operand->kind) { + case OPND_GPR: + if (operand->name[0] == 'b' || operand->name[0] == 'x') { + /* Choose r0 */ + f->assigned_value = 0; + iterate(fp, opc, fields, ix + 1); + /* Choose any GPR other than r0 */ + f->assigned_value = random_gpr(/* r0_allowed */ 0); + iterate(fp, opc, fields, ix + 1); + } else { + /* Choose any GPR */ + f->assigned_value = random_gpr(/* r0_allowed */ 1); + iterate(fp, opc, fields, ix + 1); + } + break; + + case OPND_VR: + f->assigned_value = random_vr(); // Choose any VR + iterate(fp, opc, fields, ix + 1); + break; + + case OPND_D12B: + case OPND_D12XB: + case OPND_D12LB: + case OPND_D12VB: + if (f->is_displacement) { + if (d12_val_specified) { + f->assigned_value = d12_val; + iterate(fp, opc, fields, ix + 1); + } else { + /* Choose these interesting values */ + static const long long values[] = { 0, 1, 2, 0xfff }; + + for (int i = 0; i < sizeof values / sizeof *values; ++i) { + f->assigned_value = values[i]; + iterate(fp, opc, fields, ix + 1); + } + } + } else if (f->is_length) { + /* Choose these interesting values */ + static const long long values[] = { 0, 1, 2, 255 }; + + for (int i = 0; i < sizeof values / sizeof *values; ++i) { + f->assigned_value = values[i]; + iterate(fp, opc, fields, ix + 1); + } + } else if (f->is_vr) { + /* v0 is not special AFAICT */ + f->assigned_value = random_vr(); + iterate(fp, opc, fields, ix + 1); + } else { + /* Base or index register */ + f->assigned_value = 0; + iterate(fp, opc, fields, ix + 1); + f->assigned_value = random_gpr(/* r0_allowed */ 0); + iterate(fp, opc, fields, ix + 1); + } + break; + + case OPND_D20B: + case OPND_D20XB: + if (f->is_displacement) { + if (d20_val_specified) { + f->assigned_value = d20_val; + iterate(fp, opc, fields, ix + 1); + } else { + /* Choose these interesting values */ + static const long long values[] = { + 0, 1, 2, -1, -2, 0x7ffff, -0x80000 + }; + + for (int i = 0; i < sizeof values / sizeof *values; ++i) { + f->assigned_value = values[i]; + iterate(fp, opc, fields, ix + 1); + } + } + } else { + /* base or index register */ + f->assigned_value = 0; + iterate(fp, opc, fields, ix + 1); + f->assigned_value = random_gpr(/* r0_allowed */ 0); + iterate(fp, opc, fields, ix + 1); + } + break; + + case OPND_SINT: + case OPND_PCREL: + if (sint_val_specified) { + f->assigned_value = sint_val; + iterate(fp, opc, fields, ix + 1); + } else { + if (operand->allowed_values == NULL) { + /* No constraint: Choose these interesting values */ + const long long values[] = { + 0, 1, 2, -1, -2, (1LL << (operand->num_bits - 1)) - 1, + -(1LL << (operand->num_bits - 1)) + }; + + for (int i = 0; i < sizeof values / sizeof *values; ++i) { + f->assigned_value = values[i]; + iterate(fp, opc, fields, ix + 1); + } + } else { + /* Constraint. Choose only allowed values */ + unsigned num_val = operand->allowed_values[0]; + for (int i = 1; i <= num_val; ++i) { + f->assigned_value = operand->allowed_values[i]; + iterate(fp, opc, fields, ix + 1); + } + } + } + break; + + case OPND_UINT: + if (uint_val_specified) { + f->assigned_value = uint_val; + iterate(fp, opc, fields, ix + 1); + } else { + if (operand->allowed_values == NULL) { + /* No constraint: Choose these interesting values */ + const long long values[] = { + 0, 1, 2, (1LL << operand->num_bits) - 1 + }; + + for (int i = 0; i < sizeof values / sizeof *values; ++i) { + f->assigned_value = values[i]; + iterate(fp, opc, fields, ix + 1); + } + } else { + /* Constraint. Choose only allowed values */ + unsigned num_val = operand->allowed_values[0]; + for (int i = 1; i <= num_val; ++i) { + f->assigned_value = operand->allowed_values[i]; + iterate(fp, opc, fields, ix + 1); + } + } + } + break; + + case OPND_MASK: + if (operand->allowed_values == NULL) { + /* No constraint. Choose all possible values */ + unsigned maxval = (1u << operand->num_bits) - 1; + for (int val = 0; val <= maxval; ++val) { + f->assigned_value = val; + iterate(fp, opc, fields, ix + 1); + } + } else { + /* Constraint. Choose only allowed values */ + unsigned num_val = operand->allowed_values[0]; + for (int i = 1; i <= num_val; ++i) { + f->assigned_value = operand->allowed_values[i]; + iterate(fp, opc, fields, ix + 1); + } + } + break; + + case OPND_INVALID: + default: + assert(0); + } +} + + +static void +generate(FILE *fp, const opcode *opc) +{ + /* Array of opcode fields to which we need to assign values. */ + field fields[opc->num_fields]; + field *f; + + int ix = 0; + for (int i = 0; i < opc->num_opnds; ++i) { + const opnd *operand = opc->opnds + i; + + switch (operand->kind) { + case OPND_GPR: + case OPND_VR: + case OPND_SINT: + case OPND_UINT: + case OPND_PCREL: + case OPND_MASK: + f = fields + ix++; + f->operand = operand; + break; + + case OPND_D12XB: + case OPND_D20XB: + for (int j = 1; j <= 3; ++j) { + f = fields + ix++; + f->operand = operand; + f->is_displacement = j == 1; + f->is_length = 0; + f->is_vr = 0; + } + break; + + case OPND_D12B: + case OPND_D20B: + for (int j = 1; j <= 2; ++j) { + f = fields + ix++; + f->operand = operand; + f->is_displacement = j == 1; + f->is_length = 0; + f->is_vr = 0; + } + break; + + case OPND_D12LB: + for (int j = 1; j <= 3; ++j) { + f = fields + ix++; + f->operand = operand; + f->is_displacement = j == 1; + f->is_length = j == 2; + f->is_vr = 0; + } + break; + + case OPND_D12VB: + for (int j = 1; j <= 3; ++j) { + f = fields + ix++; + f->operand = operand; + f->is_displacement = j == 1; + f->is_length = 0; + f->is_vr = j == 2; + } + break; + + case OPND_INVALID: + default: + assert(0); + } + } + assert(ix == opc->num_fields); + + iterate(fp, opc, fields, 0); +} + + +unsigned +generate_tests(const opcode *opc) +{ + srand(42); + + if (verbose) + printf("...generating testcases for '%s'\n", opc->name); + + num_tests = 0; + + char file[strlen(opc->name) + 3]; + sprintf(file, "%s.c", opc->name); + + FILE *fp = fopen(file, "w"); + if (fp == NULL) { + error("%s: fopen failed\n", file); + return 0; + } + + fprintf(fp, "void\n"); + fprintf(fp, "main(void)\n"); + fprintf(fp, "{\n"); + fprintf(fp, " asm volatile(\"%s\");\n", MARK); + generate(fp, opc); + fprintf(fp, " asm volatile(\"%s\");\n", MARK); + fprintf(fp, "}\n"); + fclose(fp); + + if (verbose) + printf("...%u testcases generated for '%s'\n", num_tests, + opc->name); + + run_cmd("%s %s %s.c", gcc, gcc_flags, opc->name); + run_cmd("%s --disassemble=%s %s.o > %s.dump", objdump, FUNCTION, + opc->name, opc->name); + + return num_tests; +} + + +static void +run_cmd(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + int need = vsnprintf((char []){ 0 }, 1, fmt, args); + va_end(args); + + char cmd[need + 1]; + va_list args2; + va_start(args2, fmt); + vsnprintf(cmd, sizeof cmd, fmt, args2); + va_end(args2); + + if (debug) + printf("Running command: %s\n", cmd); + + int rc = system(cmd); + + if (rc != 0) + error("Command '%s' failed\n", cmd); +} diff --git a/none/tests/s390x/disasm-test/main.c b/none/tests/s390x/disasm-test/main.c new file mode 100644 index 0000000000..75d007b933 --- /dev/null +++ b/none/tests/s390x/disasm-test/main.c @@ -0,0 +1,371 @@ +/* -*- mode: C; c-basic-offset: 3; -*- */ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2024-2025 Florian Krohm + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + The GNU General Public License is contained in the file COPYING. +*/ + +#include <stddef.h> // NULL +#include <stdlib.h> // exit, malloc +#include <stdio.h> // vfprintf +#include <stdarg.h> // va_list +#include <string.h> // strchr +#include <assert.h> // assert +#include <unistd.h> // unlink +#include "vex.h" // vex_init +#include "main.h" + +int verbose, debug, show_spec_exc, show_miscompares; +int d12_val, d20_val; +long long uint_val, sint_val; +int d12_val_specified, d20_val_specified; +int uint_val_specified, sint_val_specified; + +const char *gcc = "gcc"; // path to GCC +const char *objdump = "objdump"; // path to objdump +const char *gcc_flags = "-c -march=arch14"; + +#define CHECK_CLO(x, s) (strncmp(x, s, sizeof s - 1) == 0) + +static const char usage[] = + "Usage:\n\n" + "disasm-test --generate OPCODES\n" + " Generate testcases for the given opcodes and prepare objdump files.\n\n" + "disasm-test --verify FILES\n" + " Read specified objdump files and compare with VEX disassembly.\n\n" + "disasm-test --run OPCODES\n" + " Generate testcases for the given opcodes and compare the disassembly.\n\n" + "disasm-test --all\n" + " For all opcodes generate testcases and compare the disassembly.\n\n" + "disasm-test --unit-test\n" + " Run unit tests. All other command line options are ignored.\n\n" + "Additional options:\n" + " --verbose\n" + " --debug\n" + " --gcc=/path/to/gcc\n" + " --gcc-flags=FLAGS\n" + " --objdump=/path/to/objdump\n" + " --d12=INT - Use INT as value for d12 offsets\n" + " --d20=INT - Use INT as value for d20 offsets\n" + " --sint=INT - Use INT as value for signed integer fields\n" + " --uint=INT - Use INT as value for unsigned integer fields\n" + " --keep-temp - Do not remove temporary files\n" + " --summary - Output test generation summary (with --all)\n" + " --unit-test - Run unit tests\n" + " --show-spec-exc - Show insns causing specification exceptions\n" + " --no-show-miscompares - Do not show disassembly miscompares\n" + ; + +static long long get_clo_value(const char *, const char *, long long, + long long); +static void remove_temp_files(const char *); +static int opcode_has_errors(const opcode *); + +static int keep_temp = 0; +static int summary = 0; + + +/* Return code: 0 no disassembly mismatches + Return code: 1 at least one disassembly mismatch + + Specification exceptions do not influence the return code. */ +int +main(int argc, char *argv[]) +{ + int all = 0, verify = 0, generate = 0, unit_test = 0; + int num_clargs = 0; + int run = 0; + const char *clargs[argc]; + + assert(sizeof(long long) == 8); + + /* Change to line buffering */ + setlinebuf(stdout); + setlinebuf(stderr); + + show_miscompares = 1; + + /* Collect options and arguments */ + for (int i = 1; i < argc; ++i) { + const char *clo = argv[i]; + + if (CHECK_CLO(clo, "--verify")) { + verify = 1; + } else if (CHECK_CLO(clo, "--generate")) { + generate = 1; + } else if (CHECK_CLO(clo, "--all")) { + all = 1; + } else if (CHECK_CLO(clo, "--verbose")) { + verbose = 1; + } else if (CHECK_CLO(clo, "--debug")) { + debug = 1; + } else if (CHECK_CLO(clo, "--summary")) { + summary = 1; + } else if (CHECK_CLO(clo, "--unit-test")) { + unit_test = 1; + } else if (CHECK_CLO(clo, "--show-spec-exc")) { + show_spec_exc = 1; + } else if (CHECK_CLO(clo, "--no-show-miscompares")) { + show_miscompares = 0; + } else if (CHECK_CLO(clo, "--keep-temp")) { + keep_temp = 1; + } else if (CHECK_CLO(clo, "--run")) { + run = 1; + } else if (CHECK_CLO(clo, "--help")) { + printf("%s\n", usage); + return 0; + } else if (CHECK_CLO(clo, "--gcc=")) { + gcc = strchr(clo, '=') + 1; + } else if (CHECK_CLO(clo, "--gcc-flags=")) { + gcc_flags = strchr(clo, '=') + 1; + } else if (CHECK_CLO(clo, "--objdump=")) { + objdump = strchr(clo, '=') + 1; + } else if (CHECK_CLO(clo, "--d12=")) { + d12_val = get_clo_value(clo, "d12", 0, 0xfff); + } else if (CHECK_CLO(clo, "--d20=")) { + d20_val = get_clo_value(clo, "d20", -0x80000, 0x7ffff); + } else if (CHECK_CLO(clo, "--sint=")) { + /* Integer field is restricted to 32-bit */ + long long max = 0x7fffffff; + sint_val = get_clo_value(clo, "sint", -max - 1, max); + } else if (CHECK_CLO(clo, "--uint=")) { + /* Integer field is restricted to 32-bit */ + uint_val = get_clo_value(clo, "uint", 0, 0xffffffffU); + } else { + if (strncmp(clo, "--", 2) == 0) + fatal("Invalid command line option '%s'\n", clo); + clargs[num_clargs++] = clo; + } + } + + /* Check consistency of command line options */ + if (verify + generate + run + all + unit_test == 0) + fatal("One of --verify, --generate, --run, --all, or --unit-test " + "is required\n"); + if (verify + generate + run + all + unit_test != 1) + fatal("At most one of --verify, --generate, --run, --all, or " + " --unit-test can be given\n"); + + vex_init(); + + if (generate) { + if (num_clargs == 0) + fatal("Missing opcode name[s]\n"); + + for (int i = 0; i < num_clargs; ++i) { + const char *name = clargs[i]; + + opcode *opc = get_opcode_by_name(name); + + if (opc == NULL) { + error("'%s' is not a recognised opcode\n", name); + } else if (opcode_has_errors(opc)) { + error("Opcode '%s' ignored due to syntax errors\n", name); + } else { + generate_tests(opc); + release_opcode(opc); + } + } + return 0; + } + + if (verify) { + if (num_clargs == 0) + fatal("Missing file name[s]\n"); + + int num_mismatch = 0; + + for (int i = 0; i < num_clargs; ++i) { + verify_stats stats = verify_disassembly(clargs[i]); + num_mismatch += stats.num_mismatch; + } + return num_mismatch != 0; + } + + if (run) { + if (num_clargs == 0) + fatal("Missing opcode name[s]\n"); + + unsigned num_mismatch = 0; + + for (int i = 0; i < num_clargs; ++i) { + const char *name = clargs[i]; + + opcode *opc = get_opcode_by_name(name); + + if (opc == NULL) { + error("'%s' is not a recognised opcode\n", name); + } else if (opcode_has_errors(opc)) { + error("Opcode '%s' ignored due to syntax errors\n", name); + } else { + generate_tests(opc); + + char file[strlen(name) + 10]; // large enough + sprintf(file, "%s.dump", name); + + verify_stats stats = verify_disassembly(file); + num_mismatch += stats.num_mismatch; + + if (! keep_temp) + remove_temp_files(name); + release_opcode(opc); + } + } + return num_mismatch != 0; + } + + if (all) { + if (num_clargs != 0) + fatal("Excess arguments on command line\n"); + + unsigned num_tests, num_verified, num_mismatch, num_spec_exc; + num_tests = num_verified = num_mismatch = num_spec_exc = 0; + + for (int i = 0; i < num_opcodes; ++i) { + opcode *opc = get_opcode_by_index(i); // never NULL + + if (opcode_has_errors(opc)) { + error("Opcode '%s' ignored due to syntax errors\n", + opc->name); + continue; + } + num_tests += generate_tests(opc); + + char file[strlen(opc->name) + 10]; + sprintf(file, "%s.dump", opc->name); + + verify_stats stats = verify_disassembly(file); + + num_verified += stats.num_verified; + num_mismatch += stats.num_mismatch; + num_spec_exc += stats.num_spec_exc; + + if (! keep_temp) + remove_temp_files(opc->name); + release_opcode(opc); + } + if (verbose || summary) { + printf("Total: %6u tests generated\n", num_tests); + printf("Total: %6u insns verified\n", num_verified); + printf("Total: %6u disassembly mismatches\n", num_mismatch); + printf("Total: %6u specification exceptions\n", num_spec_exc); + } + return num_mismatch != 0; + } + + if (unit_test) + run_unit_tests(); + + return 0; +} + + +static void +remove_temp_files(const char *op) +{ + char file[strlen(op) + 10]; // large enough + static const char *suffix[] = { ".c", ".o", ".dump", ".vex" }; + + for (int i = 0; i < sizeof suffix / sizeof *suffix; ++i) { + sprintf(file, "%s%s", op, suffix[i]); + unlink(file); + } +} + + +/* A few convenience utilities */ +void +error(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + + +void +fatal(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + exit(EXIT_FAILURE); +} + + +void * +mallock(unsigned n) +{ + void *p = malloc(n); + + if (p == NULL) + fatal("malloc failed\n"); + return p; +} + + +char * +strsave(const char *s) +{ + return strcpy(mallock(strlen(s) + 1), s); +} + + +char * +strnsave(const char *s, unsigned len) +{ + char *p = memcpy(mallock(len + 1), s, len); + + p[len] = '\0'; + return p; +} + + +static long long +get_clo_value(const char *clo, const char *field_name, long long min, + long long max) +{ + long long value; + + const char *p = strchr(clo, '=') + 1; // succeeds + + if (sscanf(p, "%lld", &value) != 1) + fatal("%s value '%s' is not an integer\n", field_name, p); + if (value < min || value > max) + fatal("%s value '%lld' is out of range\n", field_name, value); + return value; +} + + +/* Return 1, if the given opcode has at least one invalid operand. + This indicates that there were parse errors earlier. */ +static int +opcode_has_errors(const opcode *opc) +{ + const opnd *opnds = opc->opnds; + + for (int i = 0; i < opc->num_opnds; ++i) { + if (opnds[i].kind == OPND_INVALID) + return 1; + } + return 0; +} diff --git a/none/tests/s390x/disasm-test/main.h b/none/tests/s390x/disasm-test/main.h new file mode 100644 index 0000000000..c988d8874e --- /dev/null +++ b/none/tests/s390x/disasm-test/main.h @@ -0,0 +1,101 @@ +/* -*- mode: C; c-basic-offset: 3; -*- */ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2024-2025 Florian Krohm + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + The GNU General Public License is contained in the file COPYING. +*/ + +#ifndef MAIN_H +#define MAIN_H + +/* The various kinds of operands. */ +typedef enum { + OPND_GPR, + OPND_VR, + OPND_D12LB, + OPND_D12XB, + OPND_D12VB, + OPND_D20XB, + OPND_D12B, + OPND_D20B, + OPND_SINT, + OPND_UINT, + OPND_MASK, + OPND_PCREL, + OPND_INVALID +} opnd_t; + +/* An operand */ +typedef struct { + char *name; + opnd_t kind; + unsigned num_bits; + int is_unsigned; + // NULL = no values specified. Otherwise, values[0] == #values + // and values[1..#values] are the values. + long long *allowed_values; +} opnd; + +/* An opcode */ +typedef struct { + char *name; + opnd *opnds; + unsigned num_opnds; + /* When generating a testcase this is the number of fields we + need to assign a value to */ + unsigned num_fields; +} opcode; + +typedef struct { + unsigned num_verified; + unsigned num_mismatch; + unsigned num_spec_exc; +} verify_stats; + +__attribute__((format(printf, 1, 2))) +void error(const char *, ...); +__attribute__((noreturn)) __attribute__((format(printf, 1, 2))) +void fatal(const char *, ...); + +verify_stats verify_disassembly(const char *); +unsigned generate_tests(const opcode *); +opcode *get_opcode_by_name(const char *); +opcode *get_opcode_by_index(unsigned); +void release_opcode(opcode *); +void run_unit_tests(void); + +void *mallock(unsigned); +char *strsave(const char *); +char *strnsave(const char *, unsigned); + +extern int verbose; +extern int debug; +extern int show_spec_exc; +extern int show_miscompares; +extern int d12_val, d20_val; +extern long long sint_val, uint_val; +extern int d12_val_specified, d20_val_specified; +extern int uint_val_specified, sint_val_specified; +extern unsigned num_opcodes; +extern const char *gcc; +extern const char *gcc_flags; +extern const char *objdump; + +#endif // MAIN_H diff --git a/none/tests/s390x/disasm-test/objdump.c b/none/tests/s390x/disasm-test/objdump.c new file mode 100644 index 0000000000..62d8346540 --- /dev/null +++ b/none/tests/s390x/disasm-test/objdump.c @@ -0,0 +1,237 @@ +/* -*- mode: C; c-basic-offset: 3; -*- */ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2024-2025 Florian Krohm + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + The GNU General Public License is contained in the file COPYING. +*/ + +#include <stddef.h> // NULL +#include <stdio.h> // sprintf +#include <stdlib.h> // free +#include <string.h> // strchr +#include <ctype.h> // isdigit +#include "main.h" // error +#include "objdump.h" + +static int get_nibble(const char *); +static int get_byte(const char *); + + +objdump_file * +read_objdump(const char *file) +{ + const char *function = FUNCTION; + const char *mark = MARK; + + /* Slurp file into memory */ + FILE *fp = fopen(file, "rb"); + if (fp == NULL) { + error("%s: fopen failed\n", file); + return NULL; + } + + /* Determine file size */ + int rc = fseek(fp, 0, SEEK_END); + if (rc < 0) { + error("%s: fseek failed\n", file); + return NULL; + } + + long size = ftell(fp); + if (size < 0) { + error("%s: ftell failed\n", file); + return NULL; + } + rewind(fp); + + char *const buf = mallock(size + 1); + size_t num_read = fread(buf, 1, size, fp); + if (num_read != size) { + error("%s: fread failed\n", file); + free(buf); + return NULL; + } + buf[size] = '\0'; + + fclose(fp); + + /* Determine the number of lines in the file. This number + exceeds the number of lines containing insns. */ + unsigned num_lines = 0; + + for (char *p = buf; *p; ++p) { + if (*p == '\n') { + *p = '\0'; + ++num_lines; + } + } + + /* Allocate an objdump_file. */ + objdump_file *ofile = mallock(sizeof (objdump_file)); + + ofile->filebuf = buf; + ofile->lines = mallock(num_lines * sizeof(objdump_line)); + + /* Locate the line containing <FUNCTION>: */ + char string[strlen(function) + 3 + 1]; + sprintf(string, "<%s>:", function); + + char *cur, *next = 0; // shut up, GCC + const char *end = buf + num_read; + + for (cur = buf; cur != end; cur = next) { + const char *line = cur; + next = strchr(line, '\0') + 1; + if (strstr(line, string)) + break; + } + + /* Process the lines containing insns. These are the lines between + the 1st and 2nd MARK. */ + unsigned linecnt = 0; + int marker_seen = 0; + for (cur = next; cur != end; cur = next) { + char *line = cur; + + next = strchr(line, '\0') + 1; + + char *p; + for (p = line; isspace(*p); ++p) + ; + + if (*p == '\0') continue; // blank line allowed + + unsigned address = 0; + while (*p != ':') { + address = (address << 4) + get_nibble(p); + ++p; + } + + ++p; // skip ':' + + while (isspace(*p)) + ++p; + + /* The leftmost two bits (0:1) encode the length of the insn + in bytes: + 00 -> 2 bytes, 01 -> 4 bytes, 10 -> 4 bytes, 11 -> 6 bytes. */ + unsigned char byte = get_byte(p); + unsigned insn_len = ((((byte >> 6) + 1) >> 1) + 1) << 1; + + /* Temporary buffer. */ + char insn_bytes[6] = { 0 }; + + for (int i = 0; i < insn_len; ++i) { + insn_bytes[i] = get_byte(p); + p += 3; + } + + while (isspace(*p)) // skip white space to disassembled text + ++p; + + char *dis_insn = p; + + /* Remove symbolic jump targets, if any. E.g. change + 1b68: c0 e5 ff ff fd a4 brasl %r14,16b0 <puts@plt> to + 1b68: c0 e5 ff ff fd a4 brasl %r14,16b0 + */ + p = strchr(p, '<'); + if (p) { + *p-- = '\0'; + while (isspace(*p)) // remove trailing white space + *p-- = '\0'; + } + + if (strncmp(dis_insn, mark, strlen(mark)) == 0) { + if (marker_seen) + break; // we're done + marker_seen = 1; + } else { + if (marker_seen == 1) { + /* Add the line */ + objdump_line *oline = ofile->lines + linecnt++; + oline->address = address; + oline->insn_len = insn_len; + oline->disassembled_insn = dis_insn; + memcpy(oline->insn_bytes, insn_bytes, sizeof insn_bytes); + + /* Extra byte to allow the decoder to peek past the end of + the current insn */ + // FIXME: introduce global variable that is observed in + // FIXME: the decoder which disables peeking ahead ? + oline->insn_bytes[insn_len] = 0x00; + } + } + } + + if (marker_seen == 0) { + error("%s is not a valid objdump -d file\n", file); + release_objdump(ofile); + return NULL; + } + + ofile->num_lines = linecnt; + + return ofile; +} + + +/* Free all memory allocated for the objdump file */ +void +release_objdump(objdump_file *ofile) +{ + free(ofile->filebuf); + free(ofile->lines); + free(ofile); +} + + +static int +get_nibble(const char *p) +{ + int c = *p; + + if (isdigit(c)) + return c - '0'; + + switch (tolower(c)) { + case 'a': return 10; + case 'b': return 11; + case 'c': return 12; + case 'd': return 13; + case 'e': return 14; + case 'f': return 15; + default: + break; + } + + error("%s: get_nibble failed; continuing with fingers crossed\n", p); + return 0; +} + + +static int +get_byte(const char *p) +{ + int n1 = get_nibble(p); + int n2 = get_nibble(p + 1); + + return (n1 << 4) + n2; +} diff --git a/none/tests/s390x/disasm-test/objdump.h b/none/tests/s390x/disasm-test/objdump.h new file mode 100644 index 0000000000..388943f7a7 --- /dev/null +++ b/none/tests/s390x/disasm-test/objdump.h @@ -0,0 +1,55 @@ +/* -*- mode: C; c-basic-offset: 3; -*- */ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2024-2025 Florian Krohm + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + The GNU General Public License is contained in the file COPYING. +*/ + +#ifndef OBJDUMP_H +#define OBJDUMP_H + +/* An opcode which marks the begin and end of a sequence of insns + in a testcase whose disassembly should be verified. */ +#define MARK "csch" + +/* Name of the C function containing the generated insns. */ +#define FUNCTION "main" + +/* INSN_BYTES needs an extra byte because s390_decode_and_irgen peeks + at the next instruction to handle some special case. And in case + of INSN_BYTES having only 6 elements that would be an out-of-bounds + memory access. insn_bytes[insn_len] will always be 0x00. */ +typedef struct { + unsigned address; + unsigned insn_len; + unsigned char insn_bytes[6 + 1]; + const char *disassembled_insn; // points into objdump_file::filebuf +} objdump_line; + +typedef struct { + char *filebuf; // contents of objdump file; will be modified ! + unsigned num_lines; // #lines containing insns + objdump_line *lines; +} objdump_file; + +objdump_file *read_objdump(const char *); +void release_objdump(objdump_file *); + +#endif // OBJDUMP_H diff --git a/none/tests/s390x/disasm-test/opcode.c b/none/tests/s390x/disasm-test/opcode.c new file mode 100644 index 0000000000..daa4899087 --- /dev/null +++ b/none/tests/s390x/disasm-test/opcode.c @@ -0,0 +1,964 @@ +/* -*- mode: C; c-basic-offset: 3; -*- */ + +/* + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2024-2025 Florian Krohm + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. + + The GNU General Public License is contained in the file COPYING. +*/ + +#include <stdio.h> // printf +#include <string.h> // strlen +#include <stdlib.h> // free +#include <assert.h> // assert +#include <ctype.h> // isdigit +#include "main.h" // error + +/* Each line in the initialiser is an opcode specification which + consists of two parts: opcode name and opcode operands. + Name and operands are separated by white space. Operands are + separated by ','. + + Operands should not be confused with fields. E.g. the "bc" + opcode has 2 operands (m1 and d12(x2,b2)) but 4 fields (m,d,x,b). + + The following naming conventions are observed: + + - 'r[0-9]+' denotes a GPR (4-bit wide unsigned value) + - 'b[0-9]+' denotes a GPR used as base register + - 'x[0-9]+' denotes a GPR used as index register + - 'v[0-9]+' denotes a VR (5-bit wide unsigned value) + - 'm[0-9]+' denotes a 4-bit mask (an unsigned value) + - 'l' denotes an 8-bit length (an unsigned value) + - 'l[0-9]+' denotes an 8-bit length (an unsigned value) + - 'i[0-9]+' denotes an integer + You must specify #bits and signedness like so: + i2:8u meaning: 8-bit wide, unsigned integer contents + i3:12s meaning: 12-bit wide, signed integer contents + - 'ri[0-9]+' denotes an integer that is used for PC-relative + addressing. You must specify #bits and signedness. + - 'd12' denotes a 12-bit wide displacement (an unsigned integer) + - 'd20' denotes a 20-bit wide displacement (a signed integer) + + It is also possible to restrict the values that may be assigned to an + operand (typically a mask). This is heavily used by vector opcodes. + + Example: + m4:{ 0..4 } values 0,1,2,3,4 are allowed + m4:{ 0,1,2,3,4 } same as above + m5:{ 0,1,3..7 } values 0,1,3,4,5,6,7 are allowed + + If you need to specify #bits, signedness and allowed values you + need to provide those in the order: signedness, #bits, allowed + values. E.g. i2:s8{-10..42} +*/ +static const char *opcodes[] = { + /* Unsorted list of opcodes (other than vector ops) with + extended mnemonics. See also: + Appendix J, Principles of Operation */ + "bic m1,d20(x2,b2)", + "bcr m1,r2", + "bc m1,d12(x2,b2)", + "bras r1,ri2:s16", + "brasl r1,ri2:s32", + "brc m1,ri2:s16", + "brcl m1,ri2:s32", + "brct r1,ri2:s16", + "brctg r1,ri2:s16", + "brcth r1,ri2:s32", + "brxh r1,r3,ri2:s16", + "brxhg r1,r3,ri2:s16", + "brxle r1,r3,ri2:s16", + "brxlg r1,r3,ri2:s16", + "crb r1,r2,m3,d12(b4)", + "cgrb r1,r2,m3,d12(b4)", + "crj r1,r2,m3,ri4:s16", + "cgrj r1,r2,m3,ri4:s16", + "crt r1,r2,m3", + "cgrt r1,r2,m3", + "cib r1,i2:s8,m3,d12(b4)", + "cgib r1,i2:s8,m3,d12(b4)", + "cij r1,i2:s8,m3,ri4:s16", + "cgij r1,i2:s8,m3,ri4:s16", + "cit r1,i2:s16,m3", + "cgit r1,i2:s16,m3", + "clrb r1,r2,m3,d12(b4)", + "clgrb r1,r2,m3,d12(b4)", + "clrj r1,r2,m3,ri4:s16", + "clgrj r1,r2,m3,ri4:s16", + "clrt r1,r2,m3", + "clgrt r1,r2,m3", + "clt r1,m3,d20(b2)", + "clgt r1,m3,d20(b2)", + "clib r1,i2:u8,m3,d12(b4)", + "clgib r1,i2:u8,m3,d12(b4)", + "clij r1,i2:u8,m3,ri4:s16", + "clgij r1,i2:u8,m3,ri4:s16", + "clfit r1,i2:u16,m3", + "clgit r1,i2:u16,m3", + "iilf r1,i2:u32", + "lochhi r1,i2:s16,m3", + "lochi r1,i2:s16,m3", + "locghi r1,i2:s16,m3", + "locfhr r1,r2,m3", + "locfh r1,d20(b2),m3", + "llilf r1,i2:u32", + "locr r1,r2,m3", + "locgr r1,r2,m3", + "loc r1,d20(b2),m3", + "locg r1,d20(b2),m3", + "nork r1,r2,r3", + "nogrk r1,r2,r3", + "rnsbg r1,r2,i3:u8,i4:u8,i5:u8", // FIXME un/signed i3/4/5 ? t-bit ? z-bit? + "rxsbg r1,r2,i3:u8,i4:u8,i5:u8", // FIXME ditto + "risbg r1,r2,i3:u8,i4:u8,i5:u8", // FIXME ditto + "risbgn r1,r2,i3:u8,i4:u8,i5:u8", // FIXME ditto + "risbhg r1,r2,i3:u8,i4:u8,i5:u8", // FIXME ditto + "risblg r1,r2,i3:u8,i4:u8,i5:u8", // FIXME ditto + "rosbg r1,r2,i3:u8,i4:u8,i5:u8", // FIXME ditto + "selr r1,r2,r3,m4", + "selgr r1,r2,r3,m4", + "selfhr r1,r2,r3,m4", + "stocfh r1,d20(b2),m3", + "stoc r1,d20(b2),m3", + "stocg r1,d20(b2),m3", + + /* Misc. other opcodes */ + "cksm r1,r2", // make sure, it gets disassmbled (BZ 495817) + "clcl r1,r2", // make sure, it gets disassmbled (BZ 495817) + "mvcl r1,r2", // make sure, it gets disassmbled (BZ 495817) + + // If a set of allowed values is specified for an operand this + // implies that any other value would cause a specification exception. + // UNLESS otherwise noted. + + // Chapter 21: Vector Overview and Support Instructions + "vbperm v1,v2,v3", + "vgef v1,d12(v2,b2),m3:{0,1,2,3}", + "vgeg v1,d12(v2,b2),m3:{0,1}", + "vgbm v1,i2:u16", + "vgm v1,i2:u8,i3:u8,m4:{0..3}", + "vl v1,d12(x2,b2),m3:{0,3,4}", // no spec. exc + "vlr v1,v2", + "vlrep v1,d12(x2,b2),m3:{0..3}", + "vlebrh v1,d12(x2,b2),m3:{0..7}", + "vlebrf v1,d12(x2,b2),m3:{0..3}", + "vlebrg v1,d12(x2,b2),m3:{0,1}", + "vlbrrep v1,d12(x2,b2),m3:{1..3}", + "vllebrz v1,d12(x2,b2),m3:{1..3,6}", + "vlbr v1,d12(x2,b2),m3:{1..4}", + "vleb v1,d12(x2,b2),m3", + "vleh v1,d12(x2,b2),m3:{0..7}", + "vlef v1,d12(x2,b2),m3:{0..3}", + "vleg v1,d12(x2,b2),m3:{0,1}", + "vleib v1,i2:s16,m3", + "vleih v1,i2:s16,m3:{0..7}", + "vleif v1,i2:s16,m3:{0..3}", + "vleig v1,i2:s16,m3:{0,1}", + "vler v1,d12(x2,b2),m3:{0..3}", + "vlgv r1,v3,d12(b2),m4:{0..3}", + "vllez v1,d12(x2,b2),m3:{0..3,6}", + // "vlm v1,v3,d12(b2),m4", // cannot express constraint + "vlrlr v1,r3,d12(b2)", + "vlrl v1,d12(b2),i3:u8{0..15}", + "vlbb v1,d12(x2,b2),m3:{0..6}", + "vlvg v1,r3,d12(b2),m4:{0..3}", + "vlvgp v1,r2,r3", + "vll v1,r3,d12(b2)", + "vmrh v1,v2,v3,m4:{0..3}", + "vmrl v1,v2,v3,m4:{0..3}", + "vpk v1,v2,v3,m4:{1..3}", + "vpks v1,v2,v3,m4:{1..3},m5:{0,1}", // no spec. exception f... [truncated message content] |