| Index: src/trusted/validator/x86/testing/enuminsts/vdiff.c
|
| diff --git a/src/trusted/validator/x86/testing/enuminsts/vdiff.c b/src/trusted/validator/x86/testing/enuminsts/vdiff.c
|
| deleted file mode 100644
|
| index fd905adff75f5549da85617882ebfec1a5838907..0000000000000000000000000000000000000000
|
| --- a/src/trusted/validator/x86/testing/enuminsts/vdiff.c
|
| +++ /dev/null
|
| @@ -1,557 +0,0 @@
|
| -/*
|
| - * Copyright (c) 2012 The Native Client Authors. All rights reserved.
|
| - * Use of this source code is governed by a BSD-style license that can be
|
| - * found in the LICENSE file.
|
| - */
|
| -
|
| -/* vdiff.c
|
| - * exhaustive instruction enumeration test for x86 Native Client validators.
|
| - *
|
| - * This file is based on enuminsts.c, but specialized to comparing two
|
| - * validators instead of decoders. The enuminsts.c implementation also
|
| - * had a bunch of Xed-specific logic which complicated the validator
|
| - * comparison in unhelpful ways.
|
| - */
|
| -
|
| -#ifndef NACL_TRUSTED_BUT_NOT_TCB
|
| -#error("This file is not meant for use in the TCB.")
|
| -#endif
|
| -#if NACL_WINDOWS
|
| -#define _CRT_RAND_S /* enable decl of rand_s() */
|
| -#endif
|
| -
|
| -#include "native_client/src/trusted/validator/x86/testing/enuminsts/enuminsts.h"
|
| -
|
| -#include <ctype.h>
|
| -#include <stdio.h>
|
| -#include <string.h>
|
| -#include <stdlib.h>
|
| -#include <stdarg.h>
|
| -#include <time.h>
|
| -
|
| -#include "native_client/src/include/portability_io.h"
|
| -#include "native_client/src/shared/platform/nacl_log.h"
|
| -#include "native_client/src/shared/utils/flags.h"
|
| -#include "native_client/src/trusted/validator/x86/testing/enuminsts/str_utils.h"
|
| -#include "native_client/src/trusted/validator/x86/testing/enuminsts/text2hex.h"
|
| -
|
| -/* Defines the maximum buffer size used to hold text generated for the
|
| - * disassembly of instructions, and corresponding error messages.
|
| - */
|
| -#define kBufferSize 1024
|
| -
|
| -/* When true, print more messages (i.e. verbosely). */
|
| -static Bool gVerbose = FALSE;
|
| -
|
| -/* When true, don't print out messages. That is, only print instructions
|
| - * defined by --print directives.
|
| - */
|
| -static Bool gSilent = FALSE;
|
| -
|
| -/* Count of errors that have a high certainty of being exploitable. */
|
| -static int gSawLethalError = 0;
|
| -
|
| -/* Defines the assumed text address for the test instruction */
|
| -const int kTextAddress = 0x1000;
|
| -
|
| -/* If non-negative, defines the prefix to test. */
|
| -static unsigned int gPrefix = 0;
|
| -
|
| -/* If non-negative, defines the opcode to test. */
|
| -static int gOpcode = -1;
|
| -
|
| -/* This option triggers a set of behaviors that help produce repeatable
|
| - * output, for easier diffs on the buildbots.
|
| - */
|
| -static Bool gEasyDiffMode;
|
| -
|
| -/* The production and new R-DFA validators */
|
| -NaClEnumeratorDecoder* vProd;
|
| -NaClEnumeratorDecoder* vDFA;
|
| -
|
| -/* The name of the executable (i.e. argv[0] from the command line). */
|
| -static const char *gArgv0 = "argv0";
|
| -#define FLAG_EasyDiff "--easydiff"
|
| -
|
| -/* Records that unexpected internal error occurred. */
|
| -void InternalError(const char *why) {
|
| - fprintf(stderr, "%s: Internal Error: %s\n", gArgv0, why);
|
| - gSawLethalError = 1;
|
| -}
|
| -
|
| -/* Records that a fatal (i.e. non-recoverable) error occurred. */
|
| -void ReportFatalError(const char* why) {
|
| - char buffer[kBufferSize];
|
| - SNPRINTF(buffer, kBufferSize, "%s - quitting!", why);
|
| - InternalError(buffer);
|
| - exit(1);
|
| -}
|
| -
|
| -/* Prints out the instruction each decoder disassembled */
|
| -static void PrintDisassembledInstructionVariants(NaClEnumerator *pinst,
|
| - NaClEnumerator *dinst) {
|
| - vProd->_print_inst_fn(pinst);
|
| - vDFA->_print_inst_fn(dinst);
|
| -}
|
| -
|
| -/* Prints out progress messages. */
|
| -static void PrintVProgress(const char* format, va_list ap) {
|
| - if (gSilent) {
|
| - /* Generating opcode sequences, so add special prefix so that we
|
| - * can print these out when read by the corresponding input decoder.
|
| - */
|
| - printf("#PROGRESS#");
|
| - }
|
| - vprintf(format, ap);
|
| -}
|
| -
|
| -static void PrintProgress(const char* format, ...) ATTRIBUTE_FORMAT_PRINTF(1,2);
|
| -
|
| -/* Prints out progress messages. */
|
| -static void PrintProgress(const char* format, ...) {
|
| - va_list ap;
|
| - va_start(ap, format);
|
| - PrintVProgress(format, ap);
|
| - va_end(ap);
|
| -}
|
| -
|
| -/* Report a disagreement between decoders.
|
| - */
|
| -static void DecoderError(const char *why,
|
| - NaClEnumerator *pinst,
|
| - NaClEnumerator *dinst,
|
| - const char *details) {
|
| - /* If reached, did not skip, so report the error. */
|
| - printf("**** ERROR: %s: %s\n", why, (details == NULL ? "" : details));
|
| - PrintDisassembledInstructionVariants(pinst, dinst);
|
| -}
|
| -
|
| -static void PrintBytes(FILE *f, uint8_t* bytes, size_t len) {
|
| - size_t i;
|
| - for (i = 0; i < len; i++) {
|
| - fprintf(f, "%02x", bytes[i]);
|
| - }
|
| -}
|
| -
|
| -struct vdiff_stats {
|
| - int64_t tried;
|
| - int64_t valid;
|
| - int64_t invalid;
|
| - int64_t errors;
|
| - int64_t ignored;
|
| -} gVDiffStats = {0, 0, 0, 0, 0};
|
| -
|
| -static void IncrTried(void) {
|
| - gVDiffStats.tried += 1;
|
| -}
|
| -
|
| -static void IncrValid(void) {
|
| - gVDiffStats.valid += 1;
|
| -}
|
| -
|
| -static void IncrInvalid(void) {
|
| - gVDiffStats.invalid += 1;
|
| -}
|
| -
|
| -static void IncrErrors(void) {
|
| - gVDiffStats.errors += 1;
|
| -}
|
| -
|
| -static void IncrIgnored(void) {
|
| - gVDiffStats.ignored += 1;
|
| -}
|
| -
|
| -static void PrintStats(void) {
|
| - printf("Stats:\n");
|
| - if (!gEasyDiffMode) {
|
| - printf("valid: %" NACL_PRIu64 "\n", gVDiffStats.valid);
|
| - printf("invalid: %" NACL_PRIu64 "\n", gVDiffStats.invalid);
|
| - }
|
| - printf("errors: %" NACL_PRIu64 "\n", gVDiffStats.errors);
|
| - printf("tried: %" NACL_PRIu64 "\n", gVDiffStats.tried);
|
| - printf("ignored: %" NACL_PRIu64 "\n", gVDiffStats.ignored);
|
| - printf(" =? %" NACL_PRIu64 " valid + invalid + errors + ignored\n",
|
| - gVDiffStats.valid + gVDiffStats.invalid + gVDiffStats.errors +
|
| - gVDiffStats.ignored);
|
| -}
|
| -
|
| -static void InitInst(NaClEnumerator *nacle,
|
| - uint8_t *itext, size_t nbytes)
|
| -{
|
| - memcpy(nacle->_itext, itext, nbytes);
|
| - nacle->_num_bytes = nbytes;
|
| -}
|
| -
|
| -/* Print out decodings if specified on the command line. */
|
| -/* Test comparison for a single instruction. */
|
| -static void TryOneInstruction(uint8_t *itext, size_t nbytes) {
|
| - NaClEnumerator pinst; /* for prod validator */
|
| - NaClEnumerator dinst; /* for dfa validator */
|
| - Bool prod_okay, rdfa_okay;
|
| -
|
| - IncrTried();
|
| - do {
|
| - if (gVerbose) {
|
| - printf("================");
|
| - PrintBytes(stdout, itext, nbytes);
|
| - printf("\n");
|
| - }
|
| -
|
| - /* Try to parse the sequence of test bytes. */
|
| - InitInst(&pinst, itext, nbytes);
|
| - InitInst(&dinst, itext, nbytes);
|
| - vProd->_parse_inst_fn(&pinst, kTextAddress);
|
| - vDFA->_parse_inst_fn(&dinst, kTextAddress);
|
| - prod_okay = vProd->_maybe_inst_validates_fn(&pinst);
|
| - rdfa_okay = vDFA->_maybe_inst_validates_fn(&dinst);
|
| -
|
| - if (prod_okay && rdfa_okay) {
|
| - if (vProd->_inst_length_fn(&pinst) ==
|
| - vDFA->_inst_length_fn(&dinst)) {
|
| - /* Both validators see a legal instruction, */
|
| - /* and they agree on critical details. */
|
| - IncrValid();
|
| - } else {
|
| - DecoderError("LENGTH MISMATCH", &pinst, &dinst, "");
|
| - IncrErrors();
|
| - }
|
| - } else if (prod_okay && !rdfa_okay) {
|
| - /*
|
| - * 32bit production validator by design is unable to distingush a lot of
|
| - * instructions (the ones which work only with memory or only with
|
| - * registers). To avoid commiting multimegabyte golden file don't count
|
| - * these differences as substantial. It's not a security problem if we
|
| - * reject some valid x86 instructions and if we'll lose something
|
| - * important hopefully developers will remind us.
|
| - */
|
| - if (NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 &&
|
| - NACL_TARGET_SUBARCH == 32) {
|
| - IncrIgnored();
|
| - } else {
|
| - /* Validators disagree on instruction legality */
|
| - DecoderError("VALIDATORS DISAGREE (prod accepts, RDFA rejects)",
|
| - &pinst,
|
| - &dinst,
|
| - "");
|
| - IncrErrors();
|
| - }
|
| - } else if (!prod_okay && rdfa_okay) {
|
| - /* Validators disagree on instruction legality */
|
| - DecoderError("VALIDATORS DISAGREE (prod rejects, RDFA accepts)",
|
| - &pinst,
|
| - &dinst,
|
| - "");
|
| - IncrErrors();
|
| - } else {
|
| - /* Both validators see an illegal instruction */
|
| - IncrInvalid();
|
| - }
|
| -
|
| - if (gVerbose) {
|
| - PrintDisassembledInstructionVariants(&pinst, &dinst);
|
| - }
|
| - } while (0);
|
| -}
|
| -
|
| -/* A function type for instruction "TestAll" functions.
|
| - * Parameters:
|
| - * prefix: up to four bytes of prefix.
|
| - * prefix_length: size_t on [0..4] specifying length of prefix.
|
| - * print_prefix: For easy diff of test output, avoid printing
|
| - * the value of a randomly selected REX prefix.
|
| - */
|
| -typedef void (*TestAllFunction)(const unsigned int prefix,
|
| - const size_t prefix_length,
|
| - const char* print_prefix);
|
| -
|
| -/* Create a char* rendition of a prefix string, appending bytes
|
| - * in ps. When using a randomly generated REX prefix on the bots,
|
| - * it's useful to avoid printing the actual REX prefix so that
|
| - * output can be diffed from run-to-run. For example, instead of
|
| - * printing "0F45" you might print "0FXX". Parameters:
|
| - * prefix: The part of the prefix value to print
|
| - * ps: 'postscript', string to append to prefix value
|
| - * str: where to put the ASCII version of the prefix
|
| - */
|
| -static char* StrPrefix(const unsigned int prefix, char* ps, char* str) {
|
| - sprintf(str, "%x%s", prefix, (ps == NULL) ? "" : ps);
|
| - return str;
|
| -}
|
| -
|
| -/* Enumerate and test all 24-bit opcode+modrm+sib patterns for a
|
| - * particular prefix.
|
| - */
|
| -static void TestAllWithPrefix(const unsigned int prefix,
|
| - const size_t prefix_length,
|
| - const char* print_prefix) {
|
| - const size_t kInstByteCount = NACL_ENUM_MAX_INSTRUCTION_BYTES;
|
| - const size_t kIterByteCount = 3;
|
| - InstByteArray itext;
|
| - size_t i;
|
| - int op, modrm, sib;
|
| - int min_op;
|
| - int max_op;
|
| -
|
| - if ((gPrefix > 0) && (gPrefix != prefix)) return;
|
| -
|
| - PrintProgress("TestAllWithPrefix(%s)\n", print_prefix);
|
| - /* set up prefix */
|
| - memcpy(itext, &prefix, prefix_length);
|
| - /* set up filler bytes */
|
| - for (i = prefix_length + kIterByteCount; i < kInstByteCount; i++) {
|
| - itext[i] = (uint8_t)i;
|
| - }
|
| - if (gOpcode < 0) {
|
| - min_op = 0;
|
| - max_op = 256;
|
| - } else {
|
| - min_op = gOpcode;
|
| - max_op = gOpcode + 1;
|
| - }
|
| - for (op = min_op; op < max_op; op++) {
|
| - itext[prefix_length] = op;
|
| - if (!gEasyDiffMode) PrintProgress("%02x 00 00\n", op);
|
| - for (modrm = 0; modrm < 256; modrm++) {
|
| - itext[prefix_length + 1] = modrm;
|
| - for (sib = 0; sib < 256; sib++) {
|
| - itext[prefix_length + 2] = sib;
|
| - TryOneInstruction(itext, kInstByteCount);
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -/* For 3DNow!, the operand byte goes at the end. Format is:
|
| - * 0F 0F [ModRM] [SIB] [displacement] imm8_opcode
|
| - * See AMD doc 24594, page 435.
|
| - */
|
| -static void TestAll3DNow(const unsigned int prefix,
|
| - const size_t prefix_length,
|
| - const char* print_prefix) {
|
| - const size_t kInstByteCount = NACL_ENUM_MAX_INSTRUCTION_BYTES;
|
| - const size_t kIterByteCount = 3;
|
| - InstByteArray itext;
|
| - size_t i;
|
| - int op, modrm, sib;
|
| -
|
| - if ((gPrefix > 0) && (gPrefix != prefix)) return;
|
| -
|
| - PrintProgress("TestAll3DNow(%s)\n", print_prefix);
|
| - /* set up prefix */
|
| - memcpy(itext, &prefix, prefix_length);
|
| - /* set up filler bytes */
|
| - for (i = prefix_length + kIterByteCount; i < kInstByteCount; i++) {
|
| - itext[i] = (uint8_t)i;
|
| - }
|
| -
|
| - for (op = 0; op < 256; op++) {
|
| - if (!gEasyDiffMode) PrintProgress("%02x 00 00\n", op);
|
| - /* Use opcode as fill byte, forcing iteration through 3DNow opcodes. */
|
| - for (i = prefix_length + 2; i < kIterByteCount; i++) itext[i] = op;
|
| -
|
| - for (modrm = 0; modrm < 256; modrm++) {
|
| - itext[prefix_length] = modrm;
|
| - for (sib = 0; sib < 256; sib++) {
|
| - itext[prefix_length + 1] = sib;
|
| - TryOneInstruction(itext, kInstByteCount);
|
| - }
|
| - }
|
| - }
|
| -}
|
| -
|
| -#if NACL_TARGET_SUBARCH == 64
|
| -/* REX prefixes range from 0x40 to 0x4f. */
|
| -const uint32_t kREXBase = 0x40;
|
| -const uint32_t kREXRange = 0x10;
|
| -const uint32_t kREXMax = 0x50; /* kREXBase + kREXRange */
|
| -
|
| -/* Generate a random REX prefix, to use for the entire run. */
|
| -static uint32_t RandomRexPrefix(void) {
|
| - static uint32_t static_rex_prefix = 0;
|
| -
|
| - if (0 == static_rex_prefix) {
|
| -#if NACL_LINUX || NACL_OSX
|
| - static_rex_prefix = kREXBase + (random() % kREXRange);
|
| -#elif NACL_WINDOWS
|
| - if (rand_s(&static_rex_prefix) != 0) {
|
| - ReportFatalError("rand_s() failed\n");
|
| - } else {
|
| - static_rex_prefix = kREXBase + (static_rex_prefix % kREXRange);
|
| - }
|
| -#else
|
| -# error "Unknown operating system."
|
| -#endif
|
| - }
|
| - return static_rex_prefix;
|
| -}
|
| -#endif
|
| -
|
| -#define AppendPrefixByte(oldprefix, pbyte) (((oldprefix) << 8) | (pbyte))
|
| -/* For x86-64, enhance the iteration by looping through REX prefixes.
|
| - */
|
| -static void WithREX(TestAllFunction testall,
|
| - const unsigned int prefix,
|
| - const size_t prefix_length) {
|
| - char pstr[kBufferSize];
|
| -#if NACL_TARGET_SUBARCH == 64
|
| - unsigned char irex;
|
| - unsigned int rprefix;
|
| - /* test with REX prefixes */
|
| - printf("WithREX(testall, %x, %d, %d)\n", prefix,
|
| - (int)prefix_length, gEasyDiffMode);
|
| - if (gEasyDiffMode) {
|
| - printf("With random REX prefix.\n");
|
| - irex = RandomRexPrefix();
|
| - rprefix = AppendPrefixByte(prefix, irex);
|
| - testall(rprefix, prefix_length + 1, StrPrefix(prefix, "XX", pstr));
|
| - } else {
|
| - for (irex = kREXBase; irex < kREXMax; irex++) {
|
| - rprefix = AppendPrefixByte(prefix, irex);
|
| - printf("With REX prefix %x\n", rprefix);
|
| - testall(rprefix, prefix_length + 1, StrPrefix(rprefix, "", pstr));
|
| - }
|
| - }
|
| -#endif
|
| - /* test with no REX prefix */
|
| - testall(prefix, prefix_length, StrPrefix(prefix, NULL, pstr));
|
| -}
|
| -#undef AppendPrefixByte
|
| -
|
| -/* For all prefixes, call TestAllWithPrefix() to enumrate and test
|
| - * all instructions.
|
| - */
|
| -static void TestAllInstructions(void) {
|
| - /* NOTE: Prefix byte order needs to be reversed when written as
|
| - * an integer. For example, for integer prefix 0x3a0f, 0f will
|
| - * go in instruction byte 0, and 3a in byte 1.
|
| - */
|
| - WithREX(TestAllWithPrefix, 0, 0);
|
| - WithREX(TestAllWithPrefix, 0x0f, 1); /* two-byte opcode */
|
| - WithREX(TestAllWithPrefix, 0x0ff2, 2); /* SSE2 */
|
| - WithREX(TestAllWithPrefix, 0x0ff3, 2); /* SSE */
|
| - WithREX(TestAllWithPrefix, 0x0f66, 2); /* SSE2 */
|
| - WithREX(TestAllWithPrefix, 0x380f, 2); /* SSSE3 */
|
| - WithREX(TestAllWithPrefix, 0x3a0f, 2); /* SSE4 */
|
| - WithREX(TestAllWithPrefix, 0x380f66, 3); /* SSE4+ */
|
| - WithREX(TestAllWithPrefix, 0x380ff2, 3); /* SSE4+ */
|
| - WithREX(TestAllWithPrefix, 0x3a0f66, 3); /* SSE4+ */
|
| - WithREX(TestAllWithPrefix, 0x0ff366, 3); /* SSE4+ */
|
| - WithREX(TestAll3DNow, 0x0f0f, 2);
|
| -}
|
| -
|
| -/* Used to test one instruction at a time, for example, in regression
|
| - * testing, or for instruction arguments from the command line.
|
| - */
|
| -static void TestOneInstruction(const char *asciihex) {
|
| - InstByteArray ibytes;
|
| - int nbytes;
|
| -
|
| - nbytes = Text2Bytes(ibytes, asciihex, "Command-line argument", -1);
|
| - if (nbytes == 0) return;
|
| - if (gVerbose) {
|
| - int i;
|
| - printf("trying %s (", asciihex);
|
| - for (i = 0; i < nbytes; ++i) {
|
| - printf("%02x", ibytes[i]);
|
| - }
|
| - printf(")\n");
|
| - }
|
| - TryOneInstruction(ibytes, (size_t) nbytes);
|
| -}
|
| -
|
| -/* A set of test cases that have caused problems in the past.
|
| - * This is a bit stale; most of the test cases came from xed_compare.py.
|
| - * Mostly this program has been tested by TestAllInstructions(),
|
| - * possible since this program is much faster than xed_compare.py
|
| - */
|
| -static void RunRegressionTests(void) {
|
| - TestOneInstruction("0024c2");
|
| - TestOneInstruction("017967");
|
| - TestOneInstruction("0f12c0");
|
| - TestOneInstruction("0f13c0");
|
| - TestOneInstruction("0f17c0");
|
| - TestOneInstruction("0f01c1");
|
| - TestOneInstruction("0f00300000112233445566778899aa");
|
| - TestOneInstruction("cc");
|
| - TestOneInstruction("C3");
|
| - TestOneInstruction("0f00290000112233445566778899aa");
|
| - TestOneInstruction("80e4f7");
|
| - TestOneInstruction("e9a0ffffff");
|
| - TestOneInstruction("4883ec08");
|
| - TestOneInstruction("0f00040500112233445566778899aa");
|
| - /* Below are newly discovered mistakes in call instructions, where the wrong
|
| - * byte length was required by x86-64 nacl validator.
|
| - */
|
| - TestOneInstruction("262e7e00");
|
| - TestOneInstruction("2e3e7900");
|
| - /* From the AMD manual, "An instruction may have only one REX prefix */
|
| - /* which must immediately precede the opcode or first excape byte */
|
| - /* in the instruction encoding." */
|
| - TestOneInstruction("406601d8"); /* illegal; REX before data16 */
|
| - TestOneInstruction("664001d8"); /* legal; REX after data16 */
|
| - TestOneInstruction("414001d8"); /* illegal; two REX bytes */
|
| -
|
| - /* And some tests for degenerate prefix patterns */
|
| - TestOneInstruction("666690");
|
| - TestOneInstruction("6690");
|
| - TestOneInstruction("666666666666666666666690");
|
| - TestOneInstruction("66454490");
|
| - TestOneInstruction("66454f90");
|
| - TestOneInstruction("456690");
|
| -}
|
| -
|
| -/* Define decoders that can be registered. */
|
| -extern NaClEnumeratorDecoder* RegisterNaClDecoder(void);
|
| -extern NaClEnumeratorDecoder* RegisterRagelDecoder(void);
|
| -
|
| -/* Initialize the set of available decoders. */
|
| -static void VDiffInitializeAvailableDecoders(void) {
|
| - vProd = RegisterNaClDecoder();
|
| - vDFA = RegisterRagelDecoder();
|
| -}
|
| -
|
| -static int ParseArgv(const int argc, const char* argv[]) {
|
| - int nextarg;
|
| -
|
| - gArgv0 = argv[0];
|
| - nextarg = 1;
|
| - if (nextarg < argc &&
|
| - 0 == strcmp(argv[nextarg], FLAG_EasyDiff)) {
|
| - gEasyDiffMode = TRUE;
|
| - nextarg += 1;
|
| - }
|
| - return nextarg;
|
| -}
|
| -
|
| -static char g_standard_output_buffer[4 << 10];
|
| -
|
| -int main(const int argc, const char *argv[]) {
|
| - int nextarg;
|
| -
|
| - NaClLogModuleInit();
|
| - NaClLogSetVerbosity(LOG_FATAL);
|
| - if (0 != setvbuf(stdout, g_standard_output_buffer, _IOLBF,
|
| - sizeof g_standard_output_buffer)) {
|
| - NaClLog(LOG_FATAL, "vdiff: setvbuf failed\n");
|
| - }
|
| -#if NACL_LINUX || NACL_OSX
|
| - srandom(time(NULL));
|
| -#endif
|
| - VDiffInitializeAvailableDecoders();
|
| -
|
| - nextarg = ParseArgv(argc, argv);
|
| - if (nextarg == argc) {
|
| - if (gPrefix == 0) RunRegressionTests();
|
| - TestAllInstructions();
|
| - } else {
|
| - int i;
|
| - gVerbose = TRUE;
|
| - for (i = nextarg; i < argc; ++i) {
|
| - TestOneInstruction(argv[i]);
|
| - }
|
| - }
|
| - PrintStats();
|
| -
|
| - /* exit with non-zero error code if there were errors. */
|
| - exit(gVDiffStats.errors != 0);
|
| -}
|
|
|