longjmp from a signal handler ... kosher?

L

luserXtrog

A little experiment.

Is this standard?

981(1)12:50 AM:~ 0> cat erh.c
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>

enum e_err { noerror, bad, worse, yermom };

jmp_buf env;

void error(enum e_err errval) {
longjmp(env, errval);
}

void sigfpe_handle (int sig) {
error(worse);
}

void sigsegv_handle (int sig) {
error(yermom);
}

int somethingrisky(int i) {
if (i == 4) error(bad);
return somethingrisky(i+1);
}

int somethingstupid(int d) {
return 60/d;
}

int somethingtotallyretarded(char *buf) {
int i;
for (i=0;i<1000;i++) {
memcpy(buf+i*1000,"I claim this territory in the name of
Spain.",50);
}
}

int main() {
int err;

signal(SIGFPE, sigfpe_handle);
signal(SIGSEGV, sigsegv_handle);

if ( (err=setjmp(env)) == 0) {

somethingrisky(1);

somethingstupid(0);

printf("everything's ok\n");
} else switch (err) {
case bad: printf("it's bad!\n");
somethingstupid(0);
break;
case worse: printf("it's worse!\n");
somethingtotallyretarded(15);
break;
case yermom: printf("your mother has been called\n");
break;
}

return 0;
}
982(1)12:51 AM:~ 0> make erh
cc -g -Wall erh.c -o erh
erh.c: In function 'somethingtotallyretarded':
erh.c:36: warning: control reaches end of non-void function
erh.c: In function 'main':
erh.c:56: warning: passing argument 1 of 'somethingtotallyretarded'
makes pointer from integer without a cast
985(1)12:56 AM:~ 0> erh
it's bad!
it's worse!
your mother has been called
986(1)12:56 AM:~ 0> echo luser
 
F

Francois Grieu

Is (use of longjmp from a signal handler) standard?

In read 7.14.1.1 item 5 as impying NO.

If the signal occurs other than as the result of calling the
abort or raise function, the behavior is undefined if the signal
handler refers to any object with static storage duration other
than by assigning a value to an object declared as volatile
sig_atomic_t, or the signal handler calls any function in the
standard library other than the abort function, the _Exit
function, or the signal function.

Francois Grieu
 
L

luserXtrog

In read 7.14.1.1 item 5 as impying NO.

 If the signal occurs other than as the result of calling the
 abort or raise function, the behavior is undefined if the signal
 handler refers to any object with static storage duration other
 than by assigning a value to an object declared as volatile
 sig_atomic_t, or the signal handler calls any function in the
 standard library other than the abort function, the _Exit
 function, or the signal function.

  Francois Grieu

Searching the archives, I found a brief discussion from 2001 where
it was stated that this was allowed in all drafts until the final.
But for some reason they were reluctant to insist upon it.

But I'm not aware of any substitute.

Is there some other way to do this?

looza
 
F

Francois Grieu

Le 26/08/2010 11:11, luserXtrog a écrit :
Searching the archives, I found a brief discussion from 2001 where
it was stated that this was allowed in all drafts until the final.
But for some reason they were reluctant to insist upon it.

Who are "they" in the last sentence?
But I'm not aware of any substitute.

Is there some other way to do this?

AFAIK, the standard-supported way is to set some sig_atomic_t
static/global variable in the signal handler, and test it in
explicit code, outside the signal handler, executed after
the potentially signal-raising code. That explicit code can
longjmp.

Francois Grieu
 
L

luserXtrog

Le 26/08/2010 11:11, luserXtrog a écrit :





Who are "they" in the last sentence?

The C99 committee.
AFAIK, the standard-supported way is to set some sig_atomic_t
static/global variable in the signal handler, and test it in
explicit code, outside the signal handler, executed after
the potentially signal-raising code. That explicit code can
longjmp.

But that's the very crux of the issue. There is no "after the
potentially signal-raising code" once that potential is actualized.
The signal handler returns to the very errant instruction mercilessly.

The following program enters an infinite loop between the
division by zero and the handler that flips the flag.


#include <stdbool.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


/*error handling*/
volatile bool errflag = false;
void myerror (int err) {
errflag = true;
}


/*machine*/

int acc;
void op_let (int x) { acc = x; }
void op_add (int x) { acc += x; }
void op_sub (int x) { acc -= x; }
void op_mul (int x) { acc *= x; }
void op_dvd (int x) { acc /= x; }
void op_out (int x) { printf("%d\n", acc); }
void op_inp (int x) { scanf("%d\n", &acc); }
void op_end (int x) { exit(x); }

#define OPLIST \
X(let) \
X(add) \
X(sub) \
X(mul) \
X(dvd) \
X(out) \
X(inp) \
X(end)

#define X(x) { op_ ## x, #x },
struct {
void (*op)(int);
char *name;
} oplist[] = { OPLIST };
#undef X

#define X(x) x,
enum e_op { OPLIST };
#undef X

struct inst { enum e_op op; int x; } myprogram[] = {
{ let, 10 },
{ add, 5 },
{ sub, 2 },
{ mul, 50 },
{ dvd, 0 },
{ out, 0 },
{ end, 0 },
};

void exe ( struct inst prog[] ) {
int i;
for (i=0; oplist[prog.op].op(prog.x), true; i++) {
if (errflag) {
fprintf(stderr,
"error occurred. acc = %d, instruction %s",
acc, oplist[prog.op].name);
exit (EXIT_FAILURE);
}
}
}


int main () {
signal(SIGFPE, myerror);

exe(myprogram);

return 0;
}

//eof
 
L

luserXtrog

It really seems that in order to actually *handle* an error
and continue to do something useful, the program needs
the ability to override its own normal behavior.

And if that last one didn't drive you crazy...

I get the same results with or without the define.

506(0)04:48 AM:~ 0> make erh2
cc -g -Wall erh2.c -o erh2
507(0)04:48 AM:~ 0> erh2
let 10
add 5
sub 2
mul 50
dvd 0
error occurred. acc = 650, instruction dvd 0
out 0
650
end 0
508(0)04:48 AM:~ 0> !ni
nice firefox&
[1] 2153
509(1)04:48 AM:~ 0> export CPPFLAGS=-DUSE_SETJMP
510(1)04:56 AM:~ 0> make erh2
make: `erh2' is up to date.
511(1)04:56 AM:~ 0> touch erh2.c
512(1)04:56 AM:~ 0> make erh2
cc -g -Wall -DUSE_SETJMP erh2.c -o erh2
513(1)04:56 AM:~ 0> erh2
let 10
add 5
sub 2
mul 50
dvd 0
error occurred. acc = 650, instruction dvd 0
out 0
650
end 0
514(1)04:56 AM:~ 0> cat erh2.c
#include <stdbool.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//#define USE_SETJMP


/*machine*/
int acc;
int pc;

#ifndef USE_SETJMP
/*hook*/
int *arg;
#endif


/* operators */

void op_let (int x) {
acc = x;
#ifndef USE_SETJMP
arg = &x;
#endif
}
void op_add (int x) { acc += x; }
void op_sub (int x) { acc -= x; }
void op_mul (int x) { acc *= x; }
void op_dvd (int x) { acc /= x; }
void op_out (int x) { printf("%d\n", acc); }
void op_inp (int x) { scanf("%d\n", &acc); }
void op_end (int x) { exit(x); }

#define OPLIST \
X(let) \
X(add) \
X(sub) \
X(mul) \
X(dvd) \
X(out) \
X(inp) \
X(end)

#define X(x) { op_ ## x, #x },
struct {
void (*op)(int);
char *name;
} oplist[] = { OPLIST };
#undef X

#define X(x) x,
enum e_op { OPLIST };
#undef X

struct inst { enum e_op op; int x; };


/*error handling*/
volatile bool errflag = false;
volatile struct inst errinst;
#ifdef USE_SETJMP
jmp_buf env;
#endif


/* execution */

void exe ( struct inst prog[] ) {
for (pc=0; true; pc++) {
printf("%s %d\n", oplist[prog[pc].op].name, prog[pc].x);

#ifdef USE_SETJMP
if (setjmp(env) == 0) {
#endif

oplist[prog[pc].op].op(prog[pc].x);

#ifdef USE_SETJMP
} else {
#else
if (errflag) {
errflag = false;
#endif

fprintf(stderr,
"error occurred. acc = %d, instruction %s %d\n",
acc, oplist[errinst.op].name, errinst.x);

}
}
}




struct inst myprogram[] = {
{ let, 10 },
{ add, 5 },
{ sub, 2 },
{ mul, 50 },
{ dvd, 0 },
{ out, 0 },
{ end, 0 },
};

void myerror (int err) {
errflag = true;
errinst = myprogram[pc];
myprogram[pc].x = 1; /* no effect */
#ifdef USE_SETJMP
longjmp(env, 1);
#else
*arg = 1; /* i don't want to do this but it works */
#endif
}

int main () {
signal(SIGFPE, myerror);

exe(myprogram);

return 0;
}

//eof
515(1)04:57 AM:~ 0> #whosaloozanau Q
 
F

Francois Grieu

Le 27/08/2010 10:04, luserXtrog a écrit :
The C99 committee.


But that's the very crux of the issue. There is no "after the
potentially signal-raising code" once that potential is actualized.
The signal handler returns to the very errant instruction mercilessly.

The following program enters an infinite loop between the
division by zero and the handler that flips the flag.

I guess that's justified by 7.14.1.1 item 3
If and when the [signal] function returns, if the
value of sig is SIGFPE, SIGILL, SIGSEGV, or any other
implementation-defined value corresponding to a computational
exception, the behavior is undefined;

fRANCOIS gRIEU
 
A

Alan Curry

void op_let (int x) {
acc = x;
#ifndef USE_SETJMP
arg = &x;
#endif
}

&x is the address of op_let's local copy of the parameter. As soon as
op_let returns, that local copy is gone, so your later use of *arg is
overwriting some other random item.
void op_add (int x) { acc += x; }
void op_sub (int x) { acc -= x; }
void op_mul (int x) { acc *= x; }
void op_dvd (int x) { acc /= x; }

How about this:
int op_dvd(int x)
{
if (!x)
return 0;
acc /= x;
return 1;
}
It may look more complex, but your version has hidden complexity: "may
generate SIGFPE which caller must take care of somehow". This version
just has "caller must check return value", which is simpler. All the
other ops can just return 0. Or you could build in overflow checking on
those too, so INT_MAX * INT_MAX will be detected as an error, if that
would better fit your overall design.

I'm not sure whether you're really interested in trapping div-by-zero or
just using it as a demonstration, but just in case, you should know that
division by zero isn't guaranteed to generate a signal. When I ran it,
it just generated a zero result.

If the div-by-zero was just a demonstration of the general problem of
returning from a synchronous signal handler... longjmp isn't pretty, but
I think it's the best you can do. Maybe siglongjmp. signals suck.
 
L

luserXtrog

&x is the address of op_let's local copy of the parameter. As soon as
op_let returns, that local copy is gone, so your later use of *arg is
overwriting some other random item.


How about this:
  int op_dvd(int x)
  {
    if (!x)
      return 0;
    acc /= x;
    return 1;
  }
It may look more complex, but your version has hidden complexity: "may
generate SIGFPE which caller must take care of somehow". This version
just has "caller must check return value", which is simpler. All the
other ops can just return 0. Or you could build in overflow checking on
those too, so INT_MAX * INT_MAX will be detected as an error, if that
would better fit your overall design.

I'm not sure whether you're really interested in trapping div-by-zero or
just using it as a demonstration, but just in case, you should know that
division by zero isn't guaranteed to generate a signal. When I ran it,
it just generated a zero result.

If the div-by-zero was just a demonstration of the general problem of
returning from a synchronous signal handler... longjmp isn't pretty, but
I think it's the best you can do. Maybe siglongjmp. signals suck.

Yes, it was just a demonstration. And your unease with the use of
a pointer into an expired stack frame was the desired reaction.

I'm having difficulty keeping track of the sources, but some documents
say you cannot longjmp from a signal handler and some say you can.
Some make no mention one way or the other (Debian's manpages).
It seems to be an operating system issue, rather than a language one
(and for that I apologize).

I found two that say you can:
http://www.gnu.org/s/libc/manual/html_node/Longjmp-in-Handler.html
and
Kernighan and Pike, /The Unix Programming Environment/, p.227
 
A

Alan Curry

Yes, it was just a demonstration. And your unease with the use of
a pointer into an expired stack frame was the desired reaction.

I was never uneasy. I was and am certain that it's a bug rendering the
exercise (under that set of #ifdefs) meaningless.
 
L

luserXtrog

I was never uneasy. I was and am certain that it's a bug rendering the
exercise (under that set of #ifdefs) meaningless.

<quibble>
Well, it's non-portable, certainly. But I think it fails to fulfill
the criteria for "bug"ness. It makes an assumption that the operator
functions are only ever called from the same frame level. Under that
assumption, it further assumes that automatic variables from similar
frames with identical types will reside in the same memory area.
Any of these (and many more probably) could fail to be true on some
system somewhere.
</quibble>

The whole point of the program was to demonstrate that there are
worse ways (or at least one) to recover from a synchronous error
without jumping out of the handler. This goal may have no value,
but it does have meaning.
 
A

Alan Curry

<quibble>
Well, it's non-portable, certainly. But I think it fails to fulfill
the criteria for "bug"ness. It makes an assumption that the operator
functions are only ever called from the same frame level. Under that
assumption, it further assumes that automatic variables from similar
frames with identical types will reside in the same memory area.
Any of these (and many more probably) could fail to be true on some
system somewhere.
</quibble>

For example, any system where normal argument passing is done through
designated registers instead of a stack. Actual generated ppc code
(gcc-4.3 -S -O3 -mregnames):

op_let:
stwu %r1,-16(%r1)
lis %r9,acc@ha
stw %r3,acc@l(%r9)
lis %r9,arg@ha
addi %r0,%r1,8
addi %r1,%r1,16
stw %r0,arg@l(%r9)
blr

op_add:
lis %r9,acc@ha
lwz %r0,acc@l(%r9)
add %r3,%r3,%r0
stw %r3,acc@l(%r9)
blr

op_add doesn't use the stack pointer (%r1) at all. x arrives in %r3, it's
used from there, it never had an address, and since there was no &x in the
function, it didn't need one. Not only that, op_let sets arg to point to a
location in a stack frame it created... but never actually stores a value at
that location. Why store a value in a variable that's about to disappear?
The whole point of the program was to demonstrate that there are
worse ways (or at least one) to recover from a synchronous error
without jumping out of the handler. This goal may have no value,
but it does have meaning.

Much worse. You could also use sigcontext to find the faulting instruction
and modify the registers to emulate "anything divided by zero equals 42".
 
L

luserXtrog

For example, any system where normal argument passing is done through
designated registers instead of a stack. Actual generated ppc code
(gcc-4.3 -S -O3 -mregnames):

op_let:
        stwu %r1,-16(%r1)
        lis %r9,acc@ha
        stw %r3,acc@l(%r9)
        lis %r9,arg@ha
        addi %r0,%r1,8
        addi %r1,%r1,16
        stw %r0,arg@l(%r9)
        blr

op_add:
        lis %r9,acc@ha
        lwz %r0,acc@l(%r9)
        add %r3,%r3,%r0
        stw %r3,acc@l(%r9)
        blr

op_add doesn't use the stack pointer (%r1) at all. x arrives in %r3, it's
used from there, it never had an address, and since there was no &x in the
function, it didn't need one. Not only that, op_let sets arg to point to a
location in a stack frame it created... but never actually stores a value at
that location. Why store a value in a variable that's about to disappear?


Much worse. You could also use sigcontext to find the faulting instruction
and modify the registers to emulate "anything divided by zero equals 42".

I begin to see.
I'm wondering now how one could go about this without longjmp-ing
or undefined behavior. I don't feel a need to crank up my optimization
just to see it fail. Your explication is convincing.

If I change the operator functions to receive a pointer to the
instruction itself, can I modify the value in memory (static storage
now) to alter the execution? Or is it likely as not to perform the
operation as a load followed by div and again miss the change?
This is what all that volatile nonsense is about, right?

Well, fortunately, my actual needs are to abort the operation
and return to the main loop, scheduling a call to a user error
handler. longjmp does the job and I'll just have to cross the
portability bridge if ever the project is worth porting.

Thanks for your comment.
 
T

tm

A little experiment.

Is this standard?

< snip a program which catches integer division by zero and
a segmentation violation by using signals and longjmp() >

Not all C compilers generate code which raises SIGFPE when an
integer division by zero occurs. As far as I know Borlands bcc32
and Microsofts cl do not. At least my implementation for exceptions
was not able to rely on SIGFPE when this compilers were used.

Even a division by zero with the integer literal 0 does not raise
SIGFPE when some compilers are used. Therefore it was necessary to
take this situation into account when my compiler generates C code.

Under the gcc systms Linux, Cygwin, and Mac OS X the functions
setjmp() and longjmp() did not work reliable, so I replaced them
with sigsetjmp() and siglongjmp().

AFAIK catching SIGSEGV is a very risky thing to do, since you
cannot rely on anything (even the code which handles SIGSEGV may
be corrupted). For this reason Seed7 does not have a concept of
catching a SIGSEGV. Besides that I once saw an exception handler
with the so called structured exceptions (AFAIK only available under
windows) which had the job of catching any uncaught exception
(including a segmentation violation) and creating a dump file. OTOH
in a "real operating system" this job is done by the OS itself.

Greetings Thomas Mertes

--
Seed7 Homepage: http://seed7.sourceforge.net
Seed7 - The extensible programming language: User defined statements
and operators, abstract data types, templates without special
syntax, OO with interfaces and multiple dispatch, statically typed,
interpreted or compiled, portable, runs under linux/unix/windows.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top