1 |
689 |
jeremybenn |
OVERVIEW
|
2 |
|
|
--------
|
3 |
|
|
|
4 |
|
|
This is a harness to test the atomicity of certain operations, and to
|
5 |
|
|
make sure the compiler does not introduce data races in a
|
6 |
|
|
multi-threaded environment.
|
7 |
|
|
|
8 |
|
|
The basic premise is that we set up testcases such that the thing we
|
9 |
|
|
want test, say an atomic instruction which stores a double word is in
|
10 |
|
|
a function of its own. We then run this testcase within GDB,
|
11 |
|
|
controlled by a gdb script (simulate-thread.gdb). The gdb script will
|
12 |
|
|
break on the function to be tested, and then single step through every
|
13 |
|
|
machine instruction in the function. We set this up so GDB can make a
|
14 |
|
|
couple of inferior function calls before and after each of these
|
15 |
|
|
single step instructions for a couple of purposes:
|
16 |
|
|
|
17 |
|
|
1. One of the calls simulates another thread running in the
|
18 |
|
|
process which changes or access memory.
|
19 |
|
|
|
20 |
|
|
2. The other calls are used to verify that we always get the
|
21 |
|
|
expected behavior.
|
22 |
|
|
|
23 |
|
|
For example, in the case of an atomic store, anyone looking at the
|
24 |
|
|
memory associated with an atomic variable should never see any in
|
25 |
|
|
between states. If you have an atomic long long int, and it starts
|
26 |
|
|
with the value 0, and you write the value MAX_LONG_LONG, any other
|
27 |
|
|
thread looking at that variable should never see anything other than 0
|
28 |
|
|
or MAX_LONG_LONG. If you implement the atomic write as a sequence of
|
29 |
|
|
2 stores, it is possible for another thread to read the location after
|
30 |
|
|
the first store, but before the second one is complete. That thread
|
31 |
|
|
would then see an in-between state (one word would still be 0).
|
32 |
|
|
|
33 |
|
|
We simulate this in the testcase by having GDB step through the
|
34 |
|
|
program, instruction by instruction, and after each step, making an
|
35 |
|
|
inferior function call which looks at the value of the atomic variable
|
36 |
|
|
and verifies that it sees either 0 or MAX_LONG_LONG. If it sees any
|
37 |
|
|
other value, it fails the testcase.
|
38 |
|
|
|
39 |
|
|
This way, we are *sure* there is no in between state because we
|
40 |
|
|
effectively acted like an OS and switched to another thread after
|
41 |
|
|
every single instruction of the routine is executed and looked at the
|
42 |
|
|
results each time.
|
43 |
|
|
|
44 |
|
|
We use the same idea to test for data races to see if an illegal load
|
45 |
|
|
has been hoisted, or that two parallel bitfield writes don't overlap
|
46 |
|
|
in a data race.
|
47 |
|
|
|
48 |
|
|
Below is a skeleton of how a test should look like. For more details,
|
49 |
|
|
look at the tests themselves.
|
50 |
|
|
|
51 |
|
|
ANATOMY OF A TEST
|
52 |
|
|
-----------------
|
53 |
|
|
|
54 |
|
|
/* { dg-do link } */
|
55 |
|
|
/* { dg-options "-some-flags" } */
|
56 |
|
|
/* { dg-final { simulate-thread } } */
|
57 |
|
|
|
58 |
|
|
/* NOTE: Any failure must be indicated by displaying "FAIL:". */
|
59 |
|
|
|
60 |
|
|
#include "simulate-thread.h"
|
61 |
|
|
|
62 |
|
|
/* Called before each instruction, simulating another thread executing. */
|
63 |
|
|
void simulate_thread_other_threads()
|
64 |
|
|
{
|
65 |
|
|
}
|
66 |
|
|
|
67 |
|
|
/* Called after each instruction. Returns 1 if any inconsistency is
|
68 |
|
|
found, 0 otherwise. */
|
69 |
|
|
int simulate_thread_step_verify()
|
70 |
|
|
{
|
71 |
|
|
if (some_problem)
|
72 |
|
|
{
|
73 |
|
|
printf("FAIL: reason\n");
|
74 |
|
|
return 1;
|
75 |
|
|
}
|
76 |
|
|
return 0;
|
77 |
|
|
}
|
78 |
|
|
|
79 |
|
|
/* Called at the end of the program (simulate_thread_fini == 1). Verifies
|
80 |
|
|
the state of the program and returns 1 if any inconsistency is
|
81 |
|
|
found, 0 otherwise. */
|
82 |
|
|
int simulate_thread_final_verify()
|
83 |
|
|
{
|
84 |
|
|
if (some_problem)
|
85 |
|
|
{
|
86 |
|
|
printf("FAIL: reason\n");
|
87 |
|
|
return 1;
|
88 |
|
|
}
|
89 |
|
|
return 0;
|
90 |
|
|
}
|
91 |
|
|
|
92 |
|
|
/* The gdb script will break on simulate_thread_main(), so make sure
|
93 |
|
|
GCC does not inline it, thus making the break point fail. */
|
94 |
|
|
__attribute__((noinline))
|
95 |
|
|
void simulate_thread_main()
|
96 |
|
|
{
|
97 |
|
|
/* Do stuff. */
|
98 |
|
|
}
|
99 |
|
|
|
100 |
|
|
int main()
|
101 |
|
|
{
|
102 |
|
|
|
103 |
|
|
/* Perform any setup code that will run outside of the testing
|
104 |
|
|
harness. Put code here that you do NOT want to be interrupted on
|
105 |
|
|
an instruction basis. E.g., setup code, and system library
|
106 |
|
|
calls. */
|
107 |
|
|
|
108 |
|
|
/* Do un-instrumented stuff. */
|
109 |
|
|
/* ... */
|
110 |
|
|
|
111 |
|
|
/* Start the instrumented show. */
|
112 |
|
|
simulate_thread_main();
|
113 |
|
|
|
114 |
|
|
/* Must be called at the end of the test. */
|
115 |
|
|
simulate_thread_done();
|
116 |
|
|
|
117 |
|
|
return 0;
|
118 |
|
|
}
|