1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
|
/* Infrastructure to test the quality of debug information.
Copyright (C) 2008, 2009, 2010 Free Software Foundation, Inc.
Contributed by Alexandre Oliva <aoliva@redhat.com>.
This file is part of GCC.
GCC 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 3, or (at your option)
any later version.
GCC 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 GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
/* This is a first cut at checking that debug information matches
run-time. The idea is to annotate programs with GUALCHK* macros
that guide the tests.
In the current implementation, all of the macros expand to function
calls. On the one hand, this interferes with optimizations; on the
other hand, it establishes an optimization barrier and a clear
inspection point, where previous operations (as in the abstract
machine) should have been completed and have their effects visible,
and future operations shouldn't have started yet.
In the current implementation of guality_check(), we fork a child
process that runs gdb, attaches to the parent process (the one that
called guality_check), moves up one stack frame (to the caller of
guality_check) and then examines the given expression.
If it matches the expected value, we have a PASS. If it differs,
we have a FAILure. If it is missing, we'll have a FAIL or an
UNRESOLVED depending on whether the variable or expression might be
unavailable at that point, as indicated by the third argument.
We envision a future alternate implementation with two compilation
and execution cycles, say one that runs the program and uses the
macros to log expressions and expected values, another in which the
macros expand to nothing and the logs are used to guide a debug
session that tests the values. How to identify the inspection
points in the second case is yet to be determined. It is
recommended that GUALCHK* macros be by themselves in source lines,
so that __FILE__ and __LINE__ will be usable to identify them.
*/
/* This is the type we use to pass values to guality_check. */
typedef intmax_t gualchk_t;
/* Convert a pointer or integral type to the widest integral type,
as expected by guality_check. */
#ifndef __cplusplus
#define GUALCVT(val) \
((gualchk_t)__builtin_choose_expr \
(__builtin_types_compatible_p (__typeof (val), gualchk_t), \
(val), \
__builtin_choose_expr \
(__builtin_classify_type (val) \
== __builtin_classify_type (&guality_skip), \
(uintptr_t)(val),(intptr_t)(val))))
#else
template <typename T>
inline __attribute__((always_inline)) gualchk_t
gualcvt (T *val)
{
return (uintptr_t) val;
}
template <typename T>
inline __attribute__((always_inline)) gualchk_t
gualcvt (T val)
{
return (intptr_t) val;
}
template <>
inline __attribute__((always_inline)) gualchk_t
gualcvt<gualchk_t> (gualchk_t val)
{
return val;
}
#define GUALCVT(val) gualcvt (val)
#endif
/* Attach a debugger to the current process and verify that the string
EXPR, evaluated by the debugger, yields the gualchk_t number VAL.
If the debugger cannot compute the expression, say because the
variable is unavailable, this will count as an error, unless unkok
is nonzero. */
#define GUALCHKXPRVAL(expr, val, unkok) \
guality_check ((expr), (val), (unkok))
/* Check that a debugger knows that EXPR evaluates to the run-time
value of EXPR. Unknown values are marked as acceptable,
considering that EXPR may die right after this call. This will
affect the generated code in that EXPR will be evaluated and forced
to remain live at least until right before the call to
guality_check, although not necessarily after the call. */
#define GUALCHKXPR(expr) \
GUALCHKXPRVAL (#expr, GUALCVT (expr), 1)
/* Same as GUALCHKXPR, but issue an error if the variable is optimized
away. */
#define GUALCHKVAL(expr) \
GUALCHKXPRVAL (#expr, GUALCVT (expr), 0)
/* Check that a debugger knows that EXPR evaluates to the run-time
value of EXPR. Unknown values are marked as errors, because the
value of EXPR is forced to be available right after the call, for a
range of at least one instruction. This will affect the generated
code, in that EXPR *will* be evaluated before and preserved until
after the call to guality_check. */
#define GUALCHKFLA(expr) do { \
__typeof(expr) volatile __preserve_after; \
__typeof(expr) __preserve_before = (expr); \
GUALCHKXPRVAL (#expr, GUALCVT (__preserve_before), 0); \
__preserve_after = __preserve_before; \
asm ("" : : "m" (__preserve_after)); \
} while (0)
/* GUALCHK is the simplest way to assert that debug information for an
expression matches its run-time value. Whether to force the
expression live after the call, so as to flag incompleteness
errors, can be disabled by defining GUALITY_DONT_FORCE_LIVE_AFTER.
Setting it to -1, an error is issued for optimized out variables,
even though they are not forced live. */
#if ! GUALITY_DONT_FORCE_LIVE_AFTER
#define GUALCHK(var) GUALCHKFLA(var)
#elif GUALITY_DONT_FORCE_LIVE_AFTER < 0
#define GUALCHK(var) GUALCHKVAL(var)
#else
#define GUALCHK(var) GUALCHKXPR(var)
#endif
/* The name of the GDB program, with arguments to make it quiet. This
is GUALITY_GDB_DEFAULT GUALITY_GDB_ARGS by default, but it can be
overridden by setting the GUALITY_GDB environment variable, whereas
GUALITY_GDB_DEFAULT can be overridden by setting the
GUALITY_GDB_NAME environment variable. */
static const char *guality_gdb_command;
#define GUALITY_GDB_DEFAULT "gdb"
#if defined(__unix)
# define GUALITY_GDB_REDIRECT " > /dev/null 2>&1"
#elif defined (_WIN32) || defined (MSDOS)
# define GUALITY_GDB_REDIRECT " > nul"
#else
# define GUALITY_GDB_REDIRECT ""
#endif
#define GUALITY_GDB_ARGS " -nx -nw --quiet" GUALITY_GDB_REDIRECT
/* Kinds of results communicated as exit status from child process
that runs gdb to the parent process that's being monitored. */
enum guality_counter { PASS, INCORRECT, INCOMPLETE };
/* Count of passes and errors. */
static int guality_count[INCOMPLETE+1];
/* If --guality-skip is given in the command line, all the monitoring,
forking and debugger-attaching action will be disabled. This is
useful to run the monitor program within a debugger. */
static int guality_skip;
/* This is a file descriptor to which we'll issue gdb commands to
probe and test. */
FILE *guality_gdb_input;
/* This holds the line number where we're supposed to set a
breakpoint. */
int guality_breakpoint_line;
/* GDB should set this to true once it's connected. */
int volatile guality_attached;
/* This function is the main guality program. It may actually be
defined as main, because we #define main to it afterwards. Because
of this wrapping, guality_main may not have an empty argument
list. */
extern int guality_main (int argc, char *argv[]);
static void __attribute__((noinline))
guality_check (const char *name, gualchk_t value, int unknown_ok);
/* Set things up, run guality_main, then print a summary and quit. */
int
main (int argc, char *argv[])
{
int i;
char *argv0 = argv[0];
guality_gdb_command = getenv ("GUALITY_GDB");
if (!guality_gdb_command)
{
guality_gdb_command = getenv ("GUALITY_GDB_NAME");
if (!guality_gdb_command)
guality_gdb_command = GUALITY_GDB_DEFAULT GUALITY_GDB_ARGS;
else
{
int len = strlen (guality_gdb_command) + sizeof (GUALITY_GDB_ARGS);
char *buf = (char *) __builtin_alloca (len);
strcpy (buf, guality_gdb_command);
strcat (buf, GUALITY_GDB_ARGS);
guality_gdb_command = buf;
}
}
for (i = 1; i < argc; i++)
if (strcmp (argv[i], "--guality-skip") == 0)
guality_skip = 1;
else
break;
if (!guality_skip)
{
guality_gdb_input = popen (guality_gdb_command, "w");
/* This call sets guality_breakpoint_line. */
guality_check (NULL, 0, 0);
if (!guality_gdb_input
|| fprintf (guality_gdb_input, "\
set height 0\n\
attach %i\n\
set guality_attached = 1\n\
b %i\n\
continue\n\
", (int)getpid (), guality_breakpoint_line) <= 0
|| fflush (guality_gdb_input))
{
perror ("gdb");
abort ();
}
}
argv[--i] = argv0;
guality_main (argc - i, argv + i);
i = guality_count[INCORRECT];
fprintf (stderr, "%s: %i PASS, %i FAIL, %i UNRESOLVED\n",
i ? "FAIL" : "PASS",
guality_count[PASS], guality_count[INCORRECT],
guality_count[INCOMPLETE]);
return i;
}
#define main guality_main
/* Tell the GDB child process to evaluate NAME in the caller. If it
matches VALUE, we have a PASS; if it's unknown and UNKNOWN_OK, we
have an UNRESOLVED. Otherwise, it's a FAIL. */
static void __attribute__((noinline))
guality_check (const char *name, gualchk_t value, int unknown_ok)
{
int result;
if (guality_skip)
return;
{
volatile gualchk_t xvalue = -1;
volatile int unavailable = 0;
if (name)
{
/* The sequence below cannot distinguish an optimized away
variable from one mapped to a non-lvalue zero. */
if (fprintf (guality_gdb_input, "\
up\n\
set $value1 = 0\n\
set $value1 = (%s)\n\
set $value2 = -1\n\
set $value2 = (%s)\n\
set $value3 = $value1 - 1\n\
set $value4 = $value1 + 1\n\
set $value3 = (%s)++\n\
set $value4 = --(%s)\n\
down\n\
set xvalue = $value1\n\
set unavailable = $value1 != $value2 ? -1 : $value3 != $value4 ? 1 : 0\n\
continue\n\
", name, name, name, name) <= 0
|| fflush (guality_gdb_input))
{
perror ("gdb");
abort ();
}
else if (!guality_attached)
{
unsigned int timeout = 0;
/* Give GDB some more time to attach. Wrapping around a
32-bit counter takes some seconds, it should be plenty
of time for GDB to get a chance to start up and attach,
but not long enough that, if GDB is unavailable or
broken, we'll take far too long to give up. */
while (--timeout && !guality_attached)
;
if (!timeout && !guality_attached)
{
fprintf (stderr, "gdb: took too long to attach\n");
abort ();
}
}
}
else
{
guality_breakpoint_line = __LINE__ + 5;
return;
}
/* Do NOT add lines between the __LINE__ above and the line below,
without also adjusting the added constant to match. */
if (!unavailable || (unavailable > 0 && xvalue))
{
if (xvalue == value)
result = PASS;
else
result = INCORRECT;
}
else
result = INCOMPLETE;
asm ("" : : "X" (name), "X" (value), "X" (unknown_ok), "m" (xvalue));
switch (result)
{
case PASS:
fprintf (stderr, "PASS: %s is %lli\n", name, value);
break;
case INCORRECT:
fprintf (stderr, "FAIL: %s is %lli, not %lli\n", name, xvalue, value);
break;
case INCOMPLETE:
fprintf (stderr, "%s: %s is %s, expected %lli\n",
unknown_ok ? "UNRESOLVED" : "FAIL", name,
unavailable < 0 ? "not computable" : "optimized away", value);
result = unknown_ok ? INCOMPLETE : INCORRECT;
break;
default:
abort ();
}
}
switch (result)
{
case PASS:
case INCORRECT:
case INCOMPLETE:
++guality_count[result];
break;
default:
abort ();
}
}
|