Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

longjmp from a signal handler ... kosher?

19 views
Skip to first unread message

luserXtrog

unread,
Aug 26, 2010, 1:57:37 AM8/26/10
to
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

Francois Grieu

unread,
Aug 26, 2010, 4:47:40 AM8/26/10
to
On 26/08/2010 07:57, luserXtrog wrote:
> 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

luserXtrog

unread,
Aug 26, 2010, 5:11:11 AM8/26/10
to

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

Francois Grieu

unread,
Aug 26, 2010, 5:54:25 AM8/26/10
to
Le 26/08/2010 11:11, luserXtrog a écrit :
> On Aug 26, 3:47 am, Francois Grieu <fgr...@gmail.com> wrote:
>> On 26/08/2010 07:57, luserXtrog wrote:
>>
>>> 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.
>
> 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

luserXtrog

unread,
Aug 27, 2010, 4:04:09 AM8/27/10
to
On Aug 26, 4:54 am, Francois Grieu <fgr...@gmail.com> wrote:
> Le 26/08/2010 11:11, luserXtrog a écrit :
>
>
>
> > On Aug 26, 3:47 am, Francois Grieu <fgr...@gmail.com> wrote:
> >> On 26/08/2010 07:57, luserXtrog wrote:
>
> >>> 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.
>
> > 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?
>

The C99 committee.

>
> > 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.

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[i].op].op(prog[i].x), true; i++) {
if (errflag) {
fprintf(stderr,
"error occurred. acc = %d, instruction %s",
acc, oplist[prog[i].op].name);
exit (EXIT_FAILURE);
}
}
}


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

exe(myprogram);

return 0;
}

//eof

luserXtrog

unread,
Aug 27, 2010, 5:59:50 AM8/27/10
to
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

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

Francois Grieu

unread,
Aug 27, 2010, 9:09:08 AM8/27/10
to

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

Alan Curry

unread,
Aug 27, 2010, 5:34:06 PM8/27/10
to
In article <14ce3719-16ea-410e...@l6g2000yqb.googlegroups.com>,

luserXtrog <mij...@yahoo.com> wrote:
>
>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.

--
Alan Curry

luserXtrog

unread,
Aug 28, 2010, 2:08:12 AM8/28/10
to
On Aug 27, 4:34 pm, pac...@kosh.dhis.org (Alan Curry) wrote:
> In article <14ce3719-16ea-410e-9913-bde22079d...@l6g2000yqb.googlegroups.com>,

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

--
aloha

Alan Curry

unread,
Aug 28, 2010, 3:56:48 AM8/28/10
to
In article <241f01be-c214-4094...@t2g2000yqe.googlegroups.com>,

luserXtrog <mij...@yahoo.com> wrote:
>On Aug 27, 4:34 pm, pac...@kosh.dhis.org (Alan Curry) wrote:
>>
>> &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.
>>
>> 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 was never uneasy. I was and am certain that it's a bug rendering the
exercise (under that set of #ifdefs) meaningless.

--
Alan Curry

luserXtrog

unread,
Aug 28, 2010, 5:07:13 AM8/28/10
to
On Aug 28, 2:56 am, pac...@kosh.dhis.org (Alan Curry) wrote:
> In article <241f01be-c214-4094-9e5d-98692e73f...@t2g2000yqe.googlegroups.com>,

>
> luserXtrog  <mijo...@yahoo.com> wrote:
> >On Aug 27, 4:34 pm, pac...@kosh.dhis.org (Alan Curry) wrote:
>
> >> &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.
>
> >> 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 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.

--
lxt

Alan Curry

unread,
Aug 28, 2010, 3:53:46 PM8/28/10
to
In article <bf841f71-fc58-48b5...@a36g2000yqc.googlegroups.com>,

luserXtrog <mij...@yahoo.com> wrote:
>On Aug 28, 2:56 am, pac...@kosh.dhis.org (Alan Curry) wrote:
>> In article <241f01be-c214-4094-9e5d-98692e73f...@t2g2000yqe.googlegroups.com>,
>> >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.
>>
>
><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".

--
Alan Curry

luserXtrog

unread,
Aug 29, 2010, 2:57:46 AM8/29/10
to
On Aug 28, 2:53 pm, pac...@kosh.dhis.org (Alan Curry) wrote:
> In article <bf841f71-fc58-48b5-acd0-01602c9ea...@a36g2000yqc.googlegroups.com>,

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.

--
509(1)01:57 AM:xpost 0> xpost
looza
error: undefined in OP_load

tm

unread,
Aug 29, 2010, 4:21:22 AM8/29/10
to
On 26 Aug., 07:57, luserXtrog <mijo...@yahoo.com> wrote:
> 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.

0 new messages