raise() marked __leaf__ is not C-compliant?

Tadeus Prastowo 0x66726565@gmail.com
Thu Oct 29 07:50:01 GMT 2020


On Wed, Oct 28, 2020 at 9:17 PM Adhemerval Zanella
<adhemerval.zanella@linaro.org> wrote:
>
> On 28/10/2020 16:23, Tadeus Prastowo wrote:
> > On Wed, Oct 28, 2020 at 6:34 PM Adhemerval Zanella
> > <adhemerval.zanella@linaro.org> wrote:
> >>
> >> On 28/10/2020 10:19, Tadeus Prastowo wrote:
> >>>
> >>> However, C, including C99, C11, and the latest C18 [1], says: "If a
> >>> signal handler is called, the raise function shall not return until
> >>> after the signal handler does."  And, POSIX [2] says: "If a signal
> >>> handler is called, the raise() function shall not return until after
> >>> the signal handler does."  So, the sentence "raise() definitely runs a
> >>> signal handler" is valid in a portable sense as required by the
> >>> standards, no?
> >>
> >> My understanding is it allows synchronous signals, not enforce it;
> >> and if the signal is synchronous then it should complete prior hand.
> >
> > I understand your point as the C standard says:
> >
> > [...] distinct values that are the signal numbers, each corresponding
> > to the specified condition:
> > SIGABRT [...]
> > SIGFPE [...]
> > SIGILL [...]
> > SIGINT [...]
> > SIGSEGV [...]
> > SIGTERM [...]
> > An implementation need not generate any of these signals, except as a
> > result of explicit calls to the raise function.  [...]  The complete
> > set of signals, their semantics, and their default handling is
> > implementation-defined; all signal numbers shall be positive.
> >
> > [...]
> >
> > void (*signal(int sig, void (*func)(int)))(int);
> > Description
> > [...]
> > When a signal occurs and func points to a function, it is
> > implementation-defined whether the equivalent of signal(sig, SIG_DFL);
> > is executed [...]; in the case of SIGILL, the implementation may
> > alternatively define that no action is taken.  Then the equivalent of
> > (*func)(sig); is executed.
> >
> > End quote.
> >
> > So, yes, you are right that the sentence "raise() definitely runs a
> > signal handler" is inaccurate because, as quoted above, C standard
> > allows an implementation to not run the handler that the user has
> > designated to handle the signal being generated by raise().  However,
> > if the implementation decides that "the equivalent of (*func)(sig); is
> > executed", then the C standard requires that "the raise function shall
> > not return until after the signal handler does."
>
> I agree this does make sense for *synchronous* signal triggering.  My
> point ir neither C not POSIX specify which signal should be synchronous.
> In fact, afaik neither standard does make a clearly distinction nor
> specific the expected semantic for signal "synchronicity" (it is really
> implementation defined).

I am sorry that after I re-read the quoted C standard, the correct
understanding is that the implementation is required to execute "the
equivalent of (*func)(sig);" if the registration of `func' using
signal() is successful (i.e., signal() does not return SIG_ERR).  This
is because the C standard says: "When a signal occurs and func points
to a function, it is implementation-defined whether [...].  Then the
equivalent of (*func)(sig); is executed."  Hence, if an implementation
decides not to execute `func' for a certain implementation-defined
signal (e.g., SIGKILL), then the call signal(SIGKILL, func) must
return SIG_ERR, which is already the case.  Otherwise, the C standard
requires the implementation to run the signal handler `func'.
However, before executing `func', the C standard allows the
implementation to perform some setup, such as executing the equivalent
of signal(sig, SIG_DFL);, but no matter what the setup does, the
outcome of the setup will not cancel the execution of `func' owing to
the fact that, by signal() not returning SIG_ERR, `func' is already
required to be executed after the setup, which is already the case as
demonstrated by the following program on SIGILL on which the C
standard allows an implementation to perform no setup at all.

#include <signal.h>
static int val;
void handler(int signo) {
  val = 1;
}
int main(int argc, char **argv) {
  signal(SIGILL, handler);
  raise(SIGILL);
  return val;
}

> > To conclude, in my earlier post, I meant to say: Glibc-2.30 raise()
> > may run a signal handler that is defined in the current compilation
> > unit to use static variables.  So, unless the C standard says that it
> > is an undefined behavior to access a non-volatile object with static
> > storage duration from within a signal handler that is called
> > synchronously by raise() on a normal execution path, the marking of
> > raise() with __leaf__ makes raise() non-compliant with the C standard.

Hence, while it is indeed true that the sentence "raise() definitely
runs a signal handler" is inaccurate, it is inaccurate not because the
implementation is allowed by the C standard to not run the handler
that is successfully registered using signal() but because no handler
has been registered successfully using signal() in the first place.

> I am not a language lawyer here, and I agree with you that adding leaf
> attribute on 'raise' is wrong.

Thank you for your agreement.

> But because glibc can't guarantee how
> the underlying kernel will trigger the signal in a raise syscall.

By striving to be comformant to the C standard, glibc has to guarantee
the behavior that the C standard does not state as
undefined/unspecified/implementation-defined, which in this case is
the behavior that, after a signal handler is successfully registered
using signal() and then the handled signal is generated using raise(),
"the raise function shall not return until after the signal handler
does", doesn't it?

> I still think that once you can't assume the signal will be triggered
> synchronously, you can't also assume that accessing non-volatile object
> with static storage duration from within a signal handler might not
> trigger undefined behavior.

Isn't that by the C standard, which makes no distinction between
synchronous and asynchronous signals and so allows me to not make any
assumption about them, no undefined behavior results from accessing a
non-volatile object with static storage duration from within a signal
handler whose signal is generated using raise() on the normal
execution path?

Thank you for your patience in perusing my message.

-- 
Best regards,
Tadeus


More information about the Libc-help mailing list