mirror of
https://github.com/ioacademy-jikim/debugging
synced 2025-06-08 08:26:14 +00:00
219 lines
8.1 KiB
C
219 lines
8.1 KiB
C
// This program is a thorough test of the LOADVn/STOREVn shadow memory
|
|
// operations.
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "memcheck/memcheck.h"
|
|
|
|
// All the sizes here are in *bytes*, not bits.
|
|
|
|
typedef unsigned char U1;
|
|
typedef unsigned short U2;
|
|
typedef unsigned int U4;
|
|
typedef unsigned long long U8;
|
|
|
|
typedef float F4;
|
|
typedef double F8;
|
|
|
|
#define SZB_OF_a 64
|
|
|
|
// a[] is the array in which we do our loads and stores.
|
|
// b[] is another one in which we do some copying.
|
|
U8 a [SZB_OF_a / 8]; // Type is U8 to ensure it's 8-aligned
|
|
U8 b [SZB_OF_a / 8]; // same size as a[]
|
|
|
|
// XXX: should check the error cases for SET/GET_VBITS also
|
|
|
|
// For the byte 'x', build a value of 'size' bytes from that byte, eg:
|
|
// size 1 --> x
|
|
// size 2 --> xx
|
|
// size 4 --> xxxx
|
|
// size 8 --> xxxxxxxx
|
|
// where the 0 bits are seen by Memcheck as defined, and the 1 bits are
|
|
// seen as undefined (ie. the value of each bit matches its V bit, ie. the
|
|
// resulting value is the same as its metavalue).
|
|
//
|
|
U8 build(int size, U1 byte)
|
|
{
|
|
int i;
|
|
U8 mask = 0;
|
|
U8 shres;
|
|
U8 res = 0xffffffffffffffffULL, res2;
|
|
(void)VALGRIND_MAKE_MEM_UNDEFINED(&res, 8);
|
|
assert(1 == size || 2 == size || 4 == size || 8 == size);
|
|
|
|
for (i = 0; i < size; i++) {
|
|
mask <<= 8;
|
|
mask |= (U8)byte;
|
|
}
|
|
|
|
res &= mask;
|
|
|
|
// res is now considered partially defined, but we know exactly what its
|
|
// value is (it happens to be the same as its metavalue).
|
|
|
|
(void)VALGRIND_GET_VBITS(&res, &shres, 8);
|
|
res2 = res;
|
|
(void)VALGRIND_MAKE_MEM_DEFINED(&res2, 8); // avoid the 'undefined' warning
|
|
assert(res2 == shres);
|
|
return res;
|
|
}
|
|
|
|
// Check that all the bytes in a[x..y-1] have their V byte equal
|
|
// to either 'expected_byte' or 'expected_byte_alt'.
|
|
// 'str' and 'offset' are only used for printing an error message if
|
|
// something goes wrong.
|
|
void check_all(U4 x, U4 y, U1 expected_byte, U1 expected_byte_alt,
|
|
char* str, int offset)
|
|
{
|
|
U1 sh[SZB_OF_a]; // Used for getting a[]'s V bits
|
|
int i;
|
|
|
|
(void)VALGRIND_GET_VBITS(a, sh, sizeof(a));
|
|
for (i = x; i < y; i++) {
|
|
if ( expected_byte != sh[i] && expected_byte_alt != sh[i] ) {
|
|
fprintf(stderr, "\n\nFAILURE: %s, offset %d, byte %d -- "
|
|
"is 0x%x, should be 0x%x or 0x%x\n\n",
|
|
str, offset, i, sh[i], expected_byte,
|
|
expected_byte_alt);
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int h, i, j;
|
|
U1 *undefA, expected_byte, expected_byte_alt;
|
|
|
|
if (0 == RUNNING_ON_VALGRIND) {
|
|
fprintf(stderr,
|
|
"error: this program only works when run under Valgrind\n");
|
|
exit(1);
|
|
}
|
|
|
|
// Check a[] has the expected alignment, and that it's not too high in
|
|
// the address space (which would trigger the slow cases in
|
|
// LOADVn/STOREVn) on 64-bit platforms).
|
|
assert( 0 == (long)a % 8);
|
|
if (sizeof(void*) == 8) {
|
|
assert( ((U1*)(&a[0])) < ((U1*)(32ULL * 1024*1024*1024)/*32G*/) );
|
|
}
|
|
|
|
// Check basic types have the expected sizes.
|
|
assert(1 == sizeof(U1));
|
|
assert(2 == sizeof(U2));
|
|
assert(4 == sizeof(U4));
|
|
assert(8 == sizeof(U8));
|
|
|
|
// Create an array of values that has all the possible V bit metavalues.
|
|
// Because 0 represents a defined bit, and because undefA[] is initially
|
|
// zeroed, we have the nice property that:
|
|
//
|
|
// i == undefA[i] == V_bits_of(undefA[i])
|
|
//
|
|
// which is useful for testing below.
|
|
undefA = calloc(1, 256); // one for each possible undefinedness value
|
|
(void)VALGRIND_MAKE_MEM_UNDEFINED(undefA, 256);
|
|
for (i = 0; i < 256; i++) {
|
|
undefA[i] &= i;
|
|
}
|
|
|
|
// This code does a whole lot of reads and writes of a particular size
|
|
// (NNN = 1, 2, 4 or 8), with varying alignments, of values with
|
|
// different not/partially/fully defined metavalues, and checks that the
|
|
// V bits are set in a[] as expected using GET_VBITS.
|
|
//
|
|
// 'Ty' is the type of the thing we are copying. It can be an integer
|
|
// type or an FP type. 'ITy' is the same-sized integer type (and thus
|
|
// will be the same as 'Ty' if 'ITy' is an integer type). 'ITy' is used
|
|
// when doing shifting/masking and stuff like that.
|
|
|
|
#define DO(NNN, Ty, ITy, isF4) \
|
|
fprintf(stderr, "-- NNN: %d %s %s ------------------------\n", \
|
|
NNN, #Ty, #ITy); \
|
|
/* For all of the alignments from (0..NNN-1), eg. if NNN==4, we do */ \
|
|
/* alignments of 0, 1, 2, 3. */ \
|
|
for (h = 0; h < NNN; h++) { \
|
|
\
|
|
size_t n = sizeof(a); \
|
|
size_t nN = n / sizeof(Ty); \
|
|
Ty* aN = (Ty*)a; \
|
|
Ty* bN = (Ty*)b; \
|
|
Ty* aNb = (Ty*)(((U1*)aN) + h); /* set offset from a[] */ \
|
|
Ty* bNb = (Ty*)(((U1*)bN) + h); /* set offset from b[] */ \
|
|
\
|
|
fprintf(stderr, "h = %d (checking %d..%d) ", h, h, (int)(n-NNN+h)); \
|
|
\
|
|
/* For each of the 256 possible V byte values... */ \
|
|
for (j = 0; j < 256; j++) { \
|
|
/* build the value for i (one of: i, ii, iiii, iiiiiiii) */ \
|
|
U8 tmp = build(NNN, j); \
|
|
ITy undefN_ITy = (ITy)tmp; \
|
|
Ty* undefN_Ty; \
|
|
{ /* This just checks that no overflow occurred when squeezing */ \
|
|
/* the output of build() into a variable of type 'Ty'. */ \
|
|
U8 tmpDef = tmp; \
|
|
ITy undefN_ITyDef = undefN_ITy; \
|
|
(void)VALGRIND_MAKE_MEM_DEFINED(&tmpDef, 8 ); \
|
|
(void)VALGRIND_MAKE_MEM_DEFINED(&undefN_ITyDef, NNN); \
|
|
assert(tmpDef == (U8)undefN_ITyDef); \
|
|
} \
|
|
\
|
|
/* We have to use an array for undefN_Ty -- because if we try to
|
|
* convert an integer type from build into an FP type with a
|
|
* straight cast -- eg "float f = (float)i" -- the value gets
|
|
* converted. With this pointer/array nonsense the exact bit
|
|
* pattern gets used as an FP value unchanged (that FP value is
|
|
* undoubtedly nonsense, but that's not a problem here). */ \
|
|
undefN_Ty = (Ty*)&undefN_ITy; \
|
|
if (0 == j % 32) fprintf(stderr, "%d...", j); /* progress meter */ \
|
|
\
|
|
/* A nasty exception: most machines so far (x86/PPC32/PPC64)
|
|
* don't have 32-bit floats. So 32-bit floats get cast to 64-bit
|
|
* floats. Memcheck does a PCast in this case, which means that if
|
|
* any V bits for the 32-bit float are undefined (ie. 0 != j), all
|
|
* the V bits in the 64-bit float are undefined. So account for
|
|
* this when checking. AMD64 typically does FP arithmetic on
|
|
* SSE, effectively giving it access to 32-bit FP registers. So
|
|
* in short, for floats, we have to allow either 'j' or 0xFF
|
|
* as an acceptable result. Sigh. */ \
|
|
if (isF4) { \
|
|
expected_byte = j; \
|
|
expected_byte_alt = 0 != j ? 0xFF : j; \
|
|
} else { \
|
|
expected_byte = j; \
|
|
expected_byte_alt = j; \
|
|
} \
|
|
\
|
|
/* STOREVn. Note that we use the first element of the undefN_Ty
|
|
* array, as explained above. */ \
|
|
for (i = 0; i < nN-1; i++) { aNb[i] = undefN_Ty[0]; } \
|
|
check_all(h, n-NNN+h, expected_byte, expected_byte_alt, \
|
|
"STOREVn", h); \
|
|
\
|
|
/* LOADVn -- by copying the values to one place and then back,
|
|
* we ensure that LOADVn gets exercised. */ \
|
|
for (i = 0; i < nN-1; i++) { bNb[i] = aNb[i]; } \
|
|
for (i = 0; i < nN-1; i++) { aNb[i] = bNb[i]; } \
|
|
check_all(h, n-NNN+h, expected_byte, expected_byte_alt, "LOADVn", h); \
|
|
} \
|
|
fprintf(stderr, "\n"); \
|
|
}
|
|
|
|
// For sizes 4 and 8 we do both integer and floating-point types. The
|
|
// reason being that on 32-bit machines just using integer types never
|
|
// exercises LOADV8/STOREV8 -- for integer types these loads/stores get
|
|
// broken into two 32-bit loads/stores.
|
|
DO(1, U1, U1, /*isF4*/0);
|
|
DO(2, U2, U2, /*isF4*/0);
|
|
DO(4, U4, U4, /*isF4*/0);
|
|
DO(4, F4, U4, /*isF4*/1);
|
|
DO(8, U8, U8, /*isF4*/0);
|
|
DO(8, F8, U8, /*isF4*/0);
|
|
|
|
return 0;
|
|
}
|