lgm@ODDJOB.UCHICAGO.EDU (05/01/89)
SYNOPSIS:
The '-fcaller-saves' option can cause GCC 1.35 to generate incorrect
code. Specifically: When used with the other optimizing options
-O
-fomit-frame-pointer
-fstrength-reduce
-fcombine-regs
-fforce-mem
-fforce-addr
the additional '-fcaller-saves' option can cause GCC 1.35 to attempt
to treat %fp (on the Motorola 680x0, otherwise known as %a6 )
as *both* a frame pointer and a scratch register. The resulting
machine code core-dumps. Oddly enough, in the example I list here
the *same* C function translates into either good or bad code,
depending on whether a certain other function appears above it in
the file.
MACHINE AND OS:
AT&T UNIX PC 3B1 (based on Motorola 68010 microprocessor) running
OS Version 3.5 (compatible with UNIX System V Release 2).
CONFIGURATION:
'config.gcc 3b1'
TRANSCRIPT OF COMMAND LINE:
________________
$ echo $GCCOPTS
-O -fomit-frame-pointer -fcombine-regs -fforce-mem -fforce-addr -fstrength-reduce
$ gcc -v -S $GCCOPTS -fcaller-saves bad.c
gcc version 1.35
/usr/local/lib/gcc-cpp -v -undef -D__GNUC__ -Dmc68k -Dunix -Dunixpc -D__mc68k__ -D__unix__ -D__unixpc__ -D__OPTIMIZE__ bad.c /tmp/cca17150.cpp
GNU CPP version 1.35
/usr/local/lib/gcc-cc1 /tmp/cca17150.cpp -quiet -dumpbase bad.c -fomit-frame-pointer -fcombine-regs -fforce-mem -fforce-addr -fstrength-reduce -fcaller-saves -O -version -o bad.s
GNU C version 1.35 (68k, SGS/AT&T unixpc syntax) compiled by GNU C version 1.35.
$
_______________________________________
INPUT FILE THAT GENERATES BAD CODE:
_________________
typedef unsigned char U_CHAR;
char *xmalloc (), *xrealloc (), *xcalloc (), *savestring ();
extern int cplusplus;
extern int put_out_comments;
extern int traditional;
struct file_buf {
char *fname;
int lineno;
int length;
U_CHAR *buf;
U_CHAR *bufp;
struct hashnode *macro;
struct if_stack *if_stack;
U_CHAR *free_ptr;
};
typedef struct file_buf FILE_BUF;
extern FILE_BUF outbuf;
typedef struct definition DEFINITION;
struct definition {
int nargs;
int length;
U_CHAR *expansion;
struct reflist {
struct reflist *next;
char stringify;
char raw_before;
char raw_after;
int nchars;
int argno;
} *pattern;
U_CHAR *argnames;
};
union hashval {
int ival;
char *cpval;
DEFINITION *defn;
};
enum node_type {
T_DEFINE = 1,
T_INCLUDE,
T_IFDEF,
T_IFNDEF,
T_IF,
T_ELSE,
T_PRAGMA,
T_ELIF,
T_UNDEF,
T_LINE,
T_ERROR,
T_ENDIF,
T_SCCS,
T_IDENT,
T_SPECLINE,
T_DATE,
T_FILE,
T_BASE_FILE,
T_VERSION,
T_TIME,
T_CONST,
T_MACRO,
T_DISABLED,
T_SPEC_DEFINED,
T_UNUSED
};
struct hashnode {
struct hashnode *next;
struct hashnode *prev;
struct hashnode **bucket_hdr;
enum node_type type;
int length;
U_CHAR *name;
union hashval value;
};
extern U_CHAR is_idchar[];
extern U_CHAR is_idstart[];
extern U_CHAR is_hor_space[];
extern U_CHAR is_space[];
struct if_stack {
struct if_stack *next;
char *fname;
int lineno;
int if_succeeded;
enum node_type type;
};
int
newline_fix ();
struct arglist {
struct arglist *next;
U_CHAR *name;
int length;
int argno;
};
DEFINITION *
collect_expansion (buf, end, nargs, arglist)
U_CHAR *buf, *end;
int nargs;
struct arglist *arglist;
{
DEFINITION *defn;
register U_CHAR *p, *limit, *lastp, *exp_p;
struct reflist *endpat = 0 ;
U_CHAR *concat = 0;
U_CHAR *stringify = 0;
int maxsize;
int expected_delimiter = '\0';
if (end < buf)
abort ();
limit = end;
p = buf;
while (p < limit && is_space[limit[-1]]) limit--;
while (p < limit && is_space[*p]) p++;
maxsize = (sizeof (DEFINITION)
+ 2 * (end - limit) + 2 * (p - buf)
+ (limit - p) + 3);
defn = (DEFINITION *) xcalloc (1, maxsize);
defn->nargs = nargs;
exp_p = defn->expansion = (U_CHAR *) defn + sizeof (DEFINITION);
lastp = exp_p;
p = buf;
while (p < limit && is_space[*p]) {
*exp_p++ = '\n';
*exp_p++ = *p++;
}
while (p < limit) {
int skipped_arg = 0;
register U_CHAR c = *p++;
*exp_p++ = c;
if (!traditional) {
switch (c) {
case '\'':
case '\"':
for (; p < limit && *p != c; p++) {
*exp_p++ = *p;
if (*p == '\\') {
*exp_p++ = *++p;
}
}
*exp_p++ = *p++;
break;
case '\\':
if (p < limit && *p == '#') {
exp_p--;
*exp_p++ = *p++;
} else if (p < limit) {
*exp_p++ = *p++;
}
break;
case '#':
if (p < limit && *p == '#') {
exp_p--;
while (exp_p > lastp && is_hor_space[exp_p[-1]])
--exp_p;
p++;
do { while (is_hor_space[*p]) p++; } while (0) ;
concat = p;
} else {
exp_p--;
do { while (is_hor_space[*p]) p++; } while (0) ;
if (p == limit || ! is_idstart[*p] || nargs <= 0)
error ("# operator should be followed by a macro argument name\n");
else
stringify = p;
}
break;
}
} else {
switch (c) {
case '\'':
case '\"':
if (expected_delimiter != '\0') {
if (c == expected_delimiter)
expected_delimiter = '\0';
} else
expected_delimiter = c;
break;
case '\\':
if (expected_delimiter != 0 && p < limit
&& (*p == expected_delimiter || *p == '\\')) {
*exp_p++ = *p++;
continue;
}
break;
case '/':
if (expected_delimiter != '\0')
break;
if (*p == '*') {
exp_p--;
p += 1;
while (p < limit && !(p[-2] == '*' && p[-1] == '/'))
p++;
concat = p;
}
break;
}
}
if (is_idchar[c] && nargs > 0) {
U_CHAR *id_beg = p - 1;
int id_len;
--exp_p;
while (p != limit && is_idchar[*p]) p++;
id_len = p - id_beg;
if (is_idstart[c]) {
register struct arglist *arg;
for (arg = arglist; arg != 0 ; arg = arg->next) {
struct reflist *tpat;
if (arg->name[0] == c
&& arg->length == id_len
&& strncmp (arg->name, id_beg, id_len) == 0) {
tpat = (struct reflist *) xmalloc (sizeof (struct reflist));
tpat->next = 0 ;
tpat->raw_before = concat == id_beg;
tpat->raw_after = 0;
tpat->stringify = (traditional ? expected_delimiter != '\0'
: stringify == id_beg);
if (endpat == 0 )
defn->pattern = tpat;
else
endpat->next = tpat;
endpat = tpat;
tpat->argno = arg->argno;
tpat->nchars = exp_p - lastp;
{
register U_CHAR *p1 = p;
do { while (is_hor_space[*p1]) p1++; } while (0) ;
if (p1 + 2 <= limit && p1[0] == '#' && p1[1] == '#')
tpat->raw_after = 1;
}
lastp = exp_p;
skipped_arg = 1;
break;
}
}
}
if (! skipped_arg) {
register U_CHAR *lim1 = p;
p = id_beg;
while (p != lim1)
*exp_p++ = *p++;
if (stringify == id_beg)
error ("# operator should be followed by a macro argument name\n");
}
}
}
if (limit < end) {
while (limit < end && is_space[*limit]) {
*exp_p++ = '\n';
*exp_p++ = *limit++;
}
} else if (!traditional) {
*exp_p++ = '\n';
*exp_p++ = ' ';
}
*exp_p = '\0';
defn->length = exp_p - defn->expansion;
if (defn->length + 1 > maxsize)
abort ();
return defn;
}
U_CHAR *
skip_to_end_of_comment (ip, line_counter)
register FILE_BUF *ip;
int *line_counter;
{
register U_CHAR *limit = ip->buf + ip->length;
register U_CHAR *bp = ip->bufp;
FILE_BUF *op = &outbuf;
int output = put_out_comments && !line_counter;
if (output) {
*op->bufp++ = '/';
*op->bufp++ = '*';
}
if (cplusplus && bp[-1] == '/') {
if (output) {
while (bp < limit)
if ((*op->bufp++ = *bp++) == '\n') {
bp--;
break;
}
op->bufp[-1] = '*';
*op->bufp++ = '/';
*op->bufp++ = '\n';
} else {
while (bp < limit) {
if (*bp++ == '\n') {
bp--;
break;
}
}
}
ip->bufp = bp;
return bp;
}
while (bp < limit) {
if (output)
*op->bufp++ = *bp;
switch (*bp++) {
case '\n':
if (line_counter != 0 )
++*line_counter;
if (output)
++op->lineno;
break;
case '*':
if (*bp == '\\' && bp[1] == '\n')
newline_fix (bp);
if (*bp == '/') {
if (output)
*op->bufp++ = '/';
ip->bufp = ++bp;
return bp;
}
break;
}
}
ip->bufp = bp;
return bp;
}
_________________________________
ASSEMBLY LANGUAGE OUTPUT OF COMPILATION:
______________
file "bad.c"
text
LC%0:
byte '#,0x20,'o,'p,'e,'r,'a,'t,'o,'r,0x20,'s,'h,'o,'u,'l,'d,0x20,'b
byte 'e,0x20,'f,'o,'l,'l,'o,'w,'e,'d,0x20,'b,'y,0x20,'a,0x20,'m,'a
byte 'c,'r,'o,0x20,'a,'r,'g,'u,'m,'e,'n,'t,0x20,'n,'a,'m,'e,0xa,0x0
even
global collect_expansion
collect_expansion:
link.w %a6,&-28
movm.l &0x3f3c,-(%sp)
# ASSEMBLY LANGUAGE FOR collect_expansion ABBREVIATED
movm.l -68(%a6),&0x3cfc
unlk %a6
rts
even
global skip_to_end_of_comment
skip_to_end_of_comment:
link.w %a6,&0 # %a6 set up as frame pointer
movm.l &0x383c,-(%sp)
mov.l 8(%fp),%a5
mov.l 12(%fp),%a4
mov.l 12(%a5),%d4
add.l 8(%a5),%d4
mov.l 16(%a5),%a2
lea outbuf,%fp # %fp ( %a6 ) overwritten!
clr.l %d0
tst.l put_out_comments
beq.w L%120
cmp.w %a4,&0
bne.w L%120
mov.l &1,%d0
L%120:
mov.l %d0,%d2
beq.w L%121
lea 16(%fp),%a0
mov.l (%a0),%a1
mov.b &47,(%a1)
addq.l &1,(%a0)
mov.l (%a0),%a1
mov.b &42,(%a1)
addq.l &1,(%a0)
L%121:
tst.l cplusplus
beq.w L%122
cmp.b -1(%a2),&47
bne.w L%122
tst.l %d2
beq.w L%129
cmp.l %d4,%a2
bls.w L%125
lea 16(%fp),%a1
L%127:
mov.l (%a1),%a0
mov.b (%a2)+,%d0
mov.b %d0,(%a0)
addq.l &1,(%a1)
cmp.b %d0,&10
bne.w L%124
sub.w &1,%a2
bra.w L%125
L%124:
cmp.l %d4,%a2
bhi.w L%127
L%125:
lea 16(%fp),%a0
mov.l (%a0),%a1
sub.w &1,%a1
mov.b &42,(%a1)
mov.l (%a0),%a1
mov.b &47,(%a1)
addq.l &1,(%a0)
mov.l (%a0),%a1
mov.b &10,(%a1)
addq.l &1,(%a0)
bra.w L%150
L%132:
cmp.b (%a2)+,&10
bne.w L%129
sub.w &1,%a2
bra.w L%150
L%129:
cmp.l %d4,%a2
bhi.w L%132
bra.w L%150
L%122:
cmp.l %d4,%a2
bls.w L%147
lea 16(%fp),%a3
and.l &255,%d3
L%146:
tst.l %d2
beq.w L%135
mov.l (%a3),%a0
mov.b (%a2),(%a0)
addq.l &1,(%a3)
L%135:
mov.b (%a2)+,%d3
mov.l &10,%d1
cmp.l %d1,%d3
beq.w L%137
mov.l &42,%d1
cmp.l %d1,%d3
beq.w L%140
bra.w L%133
L%137:
cmp.w %a4,&0
beq.w L%138
addq.l &1,(%a4)
L%138:
tst.l %d2
beq.w L%133
addq.l &1,4(%fp)
bra.w L%133
L%140:
cmp.b (%a2),&92
bne.w L%141
cmp.b 1(%a2),&10
bne.w L%141
mov.l %a2,-(%sp)
jsr newline_fix
addq.w &4,%sp
L%141:
cmp.b (%a2),&47
bne.w L%133
tst.l %d2
beq.w L%143
mov.l (%a3),%a0
mov.b &47,(%a0)
addq.l &1,(%a3)
L%143:
addq.w &1,%a2
bra.w L%150
L%133:
cmp.l %d4,%a2
bhi.w L%146
L%147:
L%150:
mov.l %a2,16(%a5)
mov.l %a2,%d0
movm.l -28(%a6),&0x3c1c
unlk %a6 # Overwritten %a6 fed into %sp
rts # Jump to garbage core-dumps
_________________________________
COMMENTS:
At the beginning and end of function skip_to_end_of_comment ,
register %a6 plays its familiar role of a frame pointer.
But in the "interior" of the function, %fp (which is %a6 , of course)
is written and read as a pointer to outbuf . When the function
completes, unlk %a6 stores the address of outbuf into the stack
pointer %sp ; then rts attempts to return to the address pointed
to by %sp , and a core dump results.
Interestingly, if one simply deletes function collect_expansion ,
the generated code for function skip_to_end_of_comment does *not*
attempt to use %a6 as a frame pointer - i.e., there is no conflict
and hence no core dump.
Lawrence G. Mayka
Aurora, Illinois
lgm@lmayk.UUCP
nucsrl!chinet!lmayk!lgm