[comp.sys.nsc.32k] Bug in minix setjmp.s or gcc ?

jkp@sauna.hut.fi (Jyrki Kuoppala) (02/10/91)

Matti Aarnio writes:
>  A way to force GCC to do this (normally GCC just ignores register
>keyword and uses own heuristics!) is to take address of variable.

Or to declare it volatile, or to use -traditional (with -traditional
gcc puts variables in stack by default in functions calling setjmp).
But this wouldn't have helped (I did try emacs with -traditional).

It crashed in a bit different place than without -traditional, but
crashed anyway.

The result of the emacs crash was that a subroutine clobbered
arguments in the caller routine - and the caller routine didn't call
setjmp().  Like this:

int foo (x, y)
int x;
int y;
{
	int count = x - y; /* the compiler put this in a register
				variable like r5; say count is 5 */
	/* do various things, not using r5 */
	fprintf (stderr, "Done various things, count = %d\n, count", count);
		/* count is 5 here */
	bar();	/* call bar() */
	fprintf (stderr, "Done bar(), count = %d\n, count", count);
		/* Oh wow, count is a wild number like 846378 here ! */
	baz(count); /* crash in baz() because of r5 (count) is bogus */
}

Here's another example program exhibiting the same behaviour:

#include <stdio.h>
#include <setjmp.h>

jmp_buf env;
void foo()
{
	/* called from main(), no registers used here so nothing is
		saved in prologue  */
	if (setjmp (env)) /* at first setjmp returns 0, so we go call
				bar() */
		sleep(1);
	else {
		bar();
	}
}

void bar()
{
	register int c = 3; /* we use r3 here so we save r3 in the
				stack and set it */
	fprintf (stderr, "c = %d\n", c); /* print r3 */
	longjmp(env, 1); /* do a longjmp to sleep(1) */
	sleep (1);
	/* oops, we never get here where we would restore r3 from
		stack */
}

main()
{
	/* execution starts here */
	register int a = 1; /* we allocate r3 for a */
	foo(); /* call foo, by calling convetion foo must store r3 if
			it changes it */
	fprintf (stderr, "a = %d\n", a); /* print value of a (r3), which
	magically is 3 */
}

Do you guys really say that when compiled with a proper ANSI C
compiler and linked with a proper ANSI C library a function

foo { int a=1; bar(); printf ("%d\n", a);}

could print 3 or 5 or just anything ?

Well, with the setjmp.s in the Minix distribution and gcc 1.39 it will
- the above example program outputs 'a = 3'.  With the modified setjmp.s
which saves registers it outputs 'a = 1'.

So which is at fault, the compiler or setjmp or longjmp ?  You guys
and the gcc manual say that ANSI says that setjmp doesn't store
registers so I and everybody else thinks setjmp is correct.

To be able to handle the situoation, longjmp should somehow know which
registers were pushed on the stack in the beginning of the function -
I can't think of any way for the library routine to find this out.

So it must be the compiler's fault - it must recognize we're calling
longjump (well gcc does know if a function calls setjmp, but it
doesn't seem to find out if a function calls longjmp) and execute the
code to restore the registers just before we call longjmp.

Is there something wrong with my reasoning, or shall I mail a bug
report for gcc ?

//Jyrki

ian@sibyl.eleceng.ua.oz.au (02/10/91)

Jyrki Kuoppala writes:
 > Matti Aarnio writes:
 > >  A way to force GCC to do this (normally GCC just ignores register
 > >keyword and uses own heuristics!) is to take address of variable.
 > 
 > Or to declare it volatile, or to use -traditional (with -traditional
 > gcc puts variables in stack by default in functions calling setjmp).
 > But this wouldn't have helped (I did try emacs with -traditional).
 > 
 > It crashed in a bit different place than without -traditional, but
 > crashed anyway.
 > 
 > The result of the emacs crash was that a subroutine clobbered
 > arguments in the caller routine - and the caller routine didn't call
 > setjmp().  Like this:
 > 
 > int foo (x, y)
 > int x;
 > int y;
 > {
 > 	int count = x - y; /* the compiler put this in a register
 > 				variable like r5; say count is 5 */
 > 	/* do various things, not using r5 */
 > 	fprintf (stderr, "Done various things, count = %d\n, count", count);
 > 		/* count is 5 here */
 > 	bar();	/* call bar() */
 > 	fprintf (stderr, "Done bar(), count = %d\n, count", count);
 > 		/* Oh wow, count is a wild number like 846378 here ! */
 > 	baz(count); /* crash in baz() because of r5 (count) is bogus */
 > }

How does this work? Bar calls longjump so it never gets to execute
baz.  If it crashes in baz it must have longjumped there. Are you sure
you are a) setting the jumpbuf properly b) not over writting it and c)
restoring the stack properly from the stack. If you have mangled the
stack, a debugger trace back will be very misleading.

Regarding ansi standard conformance, the main thing is that your
program should not rely on register variables of automatic variables
(unless declared volatile). If you want to make setjump save and
restore registers so obsolete programs will compile, well go ahead,
but since obsolete programs can be made to work with -traditional, I
see no real point in putting extra overhead in setjump.

Ian