* raise() marked __leaf__ is not C-compliant? @ 2020-10-27 16:57 Tadeus Prastowo 2020-10-27 18:50 ` Adhemerval Zanella 2020-10-28 8:21 ` Florian Weimer 0 siblings, 2 replies; 14+ messages in thread From: Tadeus Prastowo @ 2020-10-27 16:57 UTC (permalink / raw) To: libc-help [-- Attachment #1: Type: text/plain, Size: 2411 bytes --] Hello, To quote C18 on the signal() function (https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf), Section 7.14.1.1 Paragraph 5: 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 or thread storage duration that is not a lock-free atomic object other than by assigning a value to an object declared as volatile sig_atomic_t End quote. My understanding is that if my single-threaded program installs a signal handler using signal() and the handler is executed as a result of calling raise(), then the handler has a defined behavior when the handler accesses any object with static storage duration even though the object is not qualified using volatile and not of type sig_atomic_t. If my understanding is incorrect, I would like to have some pointer to parts of the C standard that say so. Otherwise, why glibc 2.30 implements raise() on x86_64 by using the GCC function attribute __leaf__ when GCC says (https://gcc.gnu.org/onlinedocs/gcc-9.3.0/gcc/Common-Function-Attributes.html#Common-Function-Attributes): Note that leaf functions might indirectly run a signal handler defined in the current compilation unit that uses static variables. Similarly, when lazy symbol resolution is in effect, leaf functions might invoke indirect functions whose resolver function or implementation function is defined in the current compilation unit and uses static variables. There is no standard-compliant way to write such a signal handler, resolver function, or implementation function, and the best that you can do is to remove the leaf attribute or mark all such static variables volatile. End quote. I see the use of the __leaf__ attribute by glibc 2.30 by reading /usr/include/signal.h, finding __THROW, and then reading /usr/include/sys/cdefs.h for __THROW definition, which expands to include the __leaf__ attribute. With glibc-2.30 raise() being marked __leaf__, the attached C program has an undefined behavior because it terminates when compiled with GCC using either -O0 or -Os but enters an infinite loop when using -O2. Should glibc 2.30 be compiled in a specific way to have the defined behavior guaranteed by the C standard? If yes, what configure options should be used? Thank you. -- Best regards, Tadeus [-- Attachment #2: raise.c --] [-- Type: text/x-csrc, Size: 1069 bytes --] /* Compile this as follows: * gcc -O0 -o raise raise.c * The C standard on signal() guarantees that the execution of raise will * terminate. * * Compile this as follows: * gcc -Os -o raise raise.c * The C standard on signal() guarantees that the execution of raise will * terminate. * * Compile this as follows: * gcc -O2 -o raise raise.c * The C standard on signal() guarantees that the execution of raise will * terminate. Unfortunately, glibc-2.30 raise() is marked __leaf__, and so, the * execution won't terminate on a x86_64 machine. */ #include <signal.h> #include <stdlib.h> #include <time.h> static int terminated; static void handler(int signo) { terminated = 1; } int main(int argc, char **argv) { unsigned total = 0, delta = drand48() >= argc ? 1 : 0; /* At runtime, delta will be 0 as argc is one */ signal(SIGUSR1, handler); /* This is an infinite loop unless terminated is 1 as delta is surely 0 */ for (int i = 0; !terminated && i < 100; i += delta) { total += i; raise(SIGUSR1); } return total; } ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: raise() marked __leaf__ is not C-compliant? 2020-10-27 16:57 raise() marked __leaf__ is not C-compliant? Tadeus Prastowo @ 2020-10-27 18:50 ` Adhemerval Zanella 2020-10-28 5:47 ` Tadeus Prastowo 2020-10-28 8:21 ` Florian Weimer 1 sibling, 1 reply; 14+ messages in thread From: Adhemerval Zanella @ 2020-10-27 18:50 UTC (permalink / raw) To: Tadeus Prastowo, libc-help On 27/10/2020 13:57, Tadeus Prastowo via Libc-help wrote: > Hello, > > To quote C18 on the signal() function > (https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf), > Section 7.14.1.1 Paragraph 5: > > 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 or thread storage duration that is not a > lock-free atomic object other than by assigning a value to an object > declared as volatile sig_atomic_t > > End quote. > > My understanding is that if my single-threaded program installs a > signal handler using signal() and the handler is executed as a result > of calling raise(), then the handler has a defined behavior when the > handler accesses any object with static storage duration even though > the object is not qualified using volatile and not of type > sig_atomic_t. > > If my understanding is incorrect, I would like to have some pointer to > parts of the C standard that say so. Unfortunately this is not fully correct, specially if you accessing the object outside the signal handler. As you have noticed in your example, compiler can assume the variable 'terminated' won't be modified outside the function and apply optimization that avoid read its value from memory (most likely GCC will optimize out the '!terminated'). One of the best didactic explanation I have for this is from [1]. [1] https://wiki.sei.cmu.edu/confluence/display/c/SIG31-C.+Do+not+access+shared+objects+in+signal+handlers ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: raise() marked __leaf__ is not C-compliant? 2020-10-27 18:50 ` Adhemerval Zanella @ 2020-10-28 5:47 ` Tadeus Prastowo 2020-10-28 6:13 ` Tadeus Prastowo 0 siblings, 1 reply; 14+ messages in thread From: Tadeus Prastowo @ 2020-10-28 5:47 UTC (permalink / raw) To: Adhemerval Zanella; +Cc: libc-help [-- Attachment #1: Type: text/plain, Size: 2988 bytes --] On Tue, Oct 27, 2020 at 7:50 PM Adhemerval Zanella <adhemerval.zanella@linaro.org> wrote: > > On 27/10/2020 13:57, Tadeus Prastowo via Libc-help wrote: > > > > My understanding is that if my single-threaded program installs a > > signal handler using signal() and the handler is executed as a result > > of calling raise(), then the handler has a defined behavior when the > > handler accesses any object with static storage duration even though > > the object is not qualified using volatile and not of type > > sig_atomic_t. > > > > If my understanding is incorrect, I would like to have some pointer to > > parts of the C standard that say so. > > Unfortunately this is not fully correct, specially if you accessing the > object outside the signal handler. As you have noticed in your example, > compiler can assume the variable 'terminated' won't be modified outside > the function and apply optimization that avoid read its value from > memory (most likely GCC will optimize out the '!terminated'). However, the compiler cannot assume that the variable `terminated' won't be modified outside the loop if the loop calls a function whose definition the compiler does not see (e.g., the raise() function defined by glibc). Otherwise, the compiler is wrong if the loop calls a function defined in another translation unit because the function may modify `terminated'. > One of the best didactic explanation I have for this is from [1]. > > [1] https://wiki.sei.cmu.edu/confluence/display/c/SIG31-C.+Do+not+access+shared+objects+in+signal+handlers Thank you for the reference. I have studied it but found no pointer to parts of the C standard that says that it is an undefined behavior to call a signal handler synchronously using raise() on a normal execution path (i.e., not from within another signal handler, which certainly is undefined behavior as noted in "undefined behavior 131"). I know that the safest thing to do is always to qualify such a variable with `volatile', but I think it is valid to not do so for the cases that are not stated as undefined/unspecified/implementation-defined behavior by the C standard. That is why I ask for a pointer to parts of the C standard if indeed calling a signal handler synchronously using raise() on a normal execution path is an undefined behavior. After further thought, I found out that the problem is not glibc-2.30 marking raise() with __leaf__ but the file-scope limitation of `terminated'. As I already mentioned, since `terminated' has a file-scope, the compiler can assume that `terminated' will not be modified by a function defined in another translation unit. And hence, the compiler is correct in optimizing out the `!terminated'. Once `terminated' has a global scope, the compiler correctly does not optimize it out as exemplified by the attached program. So, marking raise() with __leaf__ does not make raise() non-compliant with the C standard. Thank you for answering my question. -- Best regards, Tadeus [-- Attachment #2: raise.c --] [-- Type: text/x-csrc, Size: 945 bytes --] /* Compile this as follows: * gcc -O0 -o raise raise.c * The C standard on signal() guarantees that the execution of raise will * terminate. * * Compile this as follows: * gcc -Os -o raise raise.c * The C standard on signal() guarantees that the execution of raise will * terminate. * * Compile this as follows: * gcc -O2 -o raise raise.c * The C standard on signal() guarantees that the execution of raise will * terminate. */ #include <signal.h> #include <stdlib.h> #include <time.h> int terminated; static void handler(int signo) { terminated = 1; } int main(int argc, char **argv) { unsigned total = 0, delta = drand48() >= argc ? 1 : 0; /* At runtime, delta will be 0 as argc is one */ signal(SIGUSR1, handler); /* This is an infinite loop unless terminated is 1 as delta is surely 0 */ for (int i = 0; !terminated && i < 100; i += delta) { total += i; raise(SIGUSR1); } return total; } ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: raise() marked __leaf__ is not C-compliant? 2020-10-28 5:47 ` Tadeus Prastowo @ 2020-10-28 6:13 ` Tadeus Prastowo 2020-10-28 7:33 ` Tadeus Prastowo 0 siblings, 1 reply; 14+ messages in thread From: Tadeus Prastowo @ 2020-10-28 6:13 UTC (permalink / raw) To: Adhemerval Zanella; +Cc: libc-help Sorry that I closed this thread too soon and reopen it again now after re-reading GCC documentation because the GCC documentation thinks that raise() should not be marked __leaf__. On Wed, Oct 28, 2020 at 6:47 AM Tadeus Prastowo <0x66726565@gmail.com> wrote: > > On Tue, Oct 27, 2020 at 7:50 PM Adhemerval Zanella > <adhemerval.zanella@linaro.org> wrote: > > > > On 27/10/2020 13:57, Tadeus Prastowo via Libc-help wrote: > > > > > > My understanding is that if my single-threaded program installs a > > > signal handler using signal() and the handler is executed as a result > > > of calling raise(), then the handler has a defined behavior when the > > > handler accesses any object with static storage duration even though > > > the object is not qualified using volatile and not of type > > > sig_atomic_t. > > > > > > If my understanding is incorrect, I would like to have some pointer to > > > parts of the C standard that say so. > > > > Unfortunately this is not fully correct, specially if you accessing the > > object outside the signal handler. As you have noticed in your example, > > compiler can assume the variable 'terminated' won't be modified outside > > the function and apply optimization that avoid read its value from > > memory (most likely GCC will optimize out the '!terminated'). > > However, the compiler cannot assume that the variable `terminated' > won't be modified outside the loop if the loop calls a function whose > definition the compiler does not see (e.g., the raise() function > defined by glibc). Otherwise, the compiler is wrong if the loop calls > a function defined in another translation unit because the function > may modify `terminated'. > > > One of the best didactic explanation I have for this is from [1]. > > > > [1] https://wiki.sei.cmu.edu/confluence/display/c/SIG31-C.+Do+not+access+shared+objects+in+signal+handlers > > Thank you for the reference. > > I have studied it but found no pointer to parts of the C standard that > says that it is an undefined behavior to call a signal handler > synchronously using raise() on a normal execution path (i.e., not from > within another signal handler, which certainly is undefined behavior > as noted in "undefined behavior 131"). > > I know that the safest thing to do is always to qualify such a > variable with `volatile', but I think it is valid to not do so for the > cases that are not stated as > undefined/unspecified/implementation-defined behavior by the C > standard. That is why I ask for a pointer to parts of the C standard > if indeed calling a signal handler synchronously using raise() on a > normal execution path is an undefined behavior. > > After further thought, I found out that the problem is not glibc-2.30 > marking raise() with __leaf__ but the file-scope limitation of > `terminated'. As I already mentioned, since `terminated' has a > file-scope, the compiler can assume that `terminated' will not be > modified by a function defined in another translation unit. And > hence, the compiler is correct in optimizing out the `!terminated'. > Once `terminated' has a global scope, the compiler correctly does not > optimize it out as exemplified by the attached program. So, marking > raise() with __leaf__ does not make raise() non-compliant with the C > standard. After re-reading the GCC documentation, I think marking raise() with __leaf__ makes raise() non-compliant with the C standard, unless the C standard says that calling a signal handler synchronously using raise() on a normal execution path is an undefined behavior. The GCC documentation on __leaf__ (https://gcc.gnu.org/onlinedocs/gcc-9.3.0/gcc/Common-Function-Attributes.html#Common-Function-Attributes) says (words in square brackets are added to fit our context): Note that leaf functions might indirectly run a signal handler defined in the current compilation unit that uses static variables. [...] There is no standard-compliant way to write such [an] implementation function [, for example, glibc-2.30 raise()], and the best that you can do is to remove the leaf attribute or mark all such static variables volatile. End quote. Glibc-2.30 raise() definitely runs a signal handler, and the signal handler can be defined in the current compilation unit using static variables. So, unless the C standard says that calling a signal handler synchronously using raise() on a normal execution path is an undefined behavior, the marking of raise() with __leaf__ makes raise() non-compliant with the C standard. May I know your opinion, please? Thank you. -- Best regards, Tadeus ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: raise() marked __leaf__ is not C-compliant? 2020-10-28 6:13 ` Tadeus Prastowo @ 2020-10-28 7:33 ` Tadeus Prastowo 2020-10-28 11:53 ` Adhemerval Zanella 0 siblings, 1 reply; 14+ messages in thread From: Tadeus Prastowo @ 2020-10-28 7:33 UTC (permalink / raw) To: Adhemerval Zanella; +Cc: libc-help Allow me to fix some mistakes in my previous e-mails: On Wed, Oct 28, 2020 at 7:13 AM Tadeus Prastowo <0x66726565@gmail.com> wrote: > > Sorry that I closed this thread too soon and reopen it again now after > re-reading GCC documentation because the GCC documentation thinks that > raise() should not be marked __leaf__. > > On Wed, Oct 28, 2020 at 6:47 AM Tadeus Prastowo <0x66726565@gmail.com> wrote: > > > > On Tue, Oct 27, 2020 at 7:50 PM Adhemerval Zanella > > <adhemerval.zanella@linaro.org> wrote: > > > > > > On 27/10/2020 13:57, Tadeus Prastowo via Libc-help wrote: > > > > > > > > My understanding is that if my single-threaded program installs a > > > > signal handler using signal() and the handler is executed as a result > > > > of calling raise(), then the handler has a defined behavior when the > > > > handler accesses any object with static storage duration even though > > > > the object is not qualified using volatile and not of type > > > > sig_atomic_t. > > > > > > > > If my understanding is incorrect, I would like to have some pointer to > > > > parts of the C standard that say so. > > > > > > Unfortunately this is not fully correct, specially if you accessing the > > > object outside the signal handler. As you have noticed in your example, > > > compiler can assume the variable 'terminated' won't be modified outside > > > the function and apply optimization that avoid read its value from > > > memory (most likely GCC will optimize out the '!terminated'). > > > > However, the compiler cannot assume that the variable `terminated' > > won't be modified outside the loop if the loop calls a function whose > > definition the compiler does not see (e.g., the raise() function > > defined by glibc). Otherwise, the compiler is wrong if the loop calls > > a function defined in another translation unit because the function > > may modify `terminated'. > > > > > One of the best didactic explanation I have for this is from [1]. > > > > > > [1] https://wiki.sei.cmu.edu/confluence/display/c/SIG31-C.+Do+not+access+shared+objects+in+signal+handlers > > > > Thank you for the reference. > > > > I have studied it but found no pointer to parts of the C standard that > > says that it is an undefined behavior to call a signal handler > > synchronously using raise() on a normal execution path (i.e., not from > > within another signal handler, which certainly is undefined behavior > > as noted in "undefined behavior 131"). I meant to say: I have studied it but found no pointer to parts of the C standard that 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 using raise() on a normal execution path (i.e., the note "undefined behavior 131" only talks about an undefined behavior that results from calling raise() from within a signal handler). > > I know that the safest thing to do is always to qualify such a > > variable with `volatile', but I think it is valid to not do so for the > > cases that are not stated as > > undefined/unspecified/implementation-defined behavior by the C > > standard. > > That is why I ask for a pointer to parts of the C standard > > if indeed calling a signal handler synchronously using raise() on a > > normal execution path is an undefined behavior. I meant to say: That is why I ask for a pointer to parts of the C standard if indeed accessing a non-volatile object with static storage duration from within a signal handler that is called synchronously using raise() on a normal execution path is an undefined behavior. > > After further thought, I found out that the problem is not glibc-2.30 > > marking raise() with __leaf__ but the file-scope limitation of > > `terminated'. As I already mentioned, since `terminated' has a > > file-scope, the compiler can assume that `terminated' will not be > > modified by a function defined in another translation unit. And > > hence, the compiler is correct in optimizing out the `!terminated'. > > Once `terminated' has a global scope, the compiler correctly does not > > optimize it out as exemplified by the attached program. So, marking > > raise() with __leaf__ does not make raise() non-compliant with the C > > standard. > > After re-reading the GCC documentation, I think marking raise() with > __leaf__ makes raise() non-compliant with the C standard, unless the C > standard says that calling a signal handler synchronously using > raise() on a normal execution path is an undefined behavior. I meant to say: After re-reading the GCC documentation, I think marking raise() with __leaf__ makes raise() non-compliant with the C standard, unless the C standard says that accessing a non-volatile object with static storage duration from within a signal handler that is called synchronously using raise() on a normal execution path is an undefined behavior. > The GCC documentation on __leaf__ > (https://gcc.gnu.org/onlinedocs/gcc-9.3.0/gcc/Common-Function-Attributes.html#Common-Function-Attributes) > says (words in square brackets are added to fit our context): > > Note that leaf functions might indirectly run a signal handler defined > in the current compilation unit that uses static variables. [...] > There is no standard-compliant way to write such [an] implementation > function [, for example, glibc-2.30 raise()], and the best that you > can do is to remove the leaf attribute or mark all such static > variables volatile. > > End quote. > > Glibc-2.30 raise() definitely runs a signal handler, and the signal > handler can be defined in the current compilation unit using static > variables. So, unless the C standard says that calling a signal > handler synchronously using raise() on a normal execution path is an > undefined behavior, the marking of raise() with __leaf__ makes raise() > non-compliant with the C standard. I meant to say: Glibc-2.30 raise() definitely runs a signal handler, and the signal handler can be defined in the current compilation unit to use static variables. So, unless the C standard says that accessing a non-volatile object with static storage duration from within a signal handler that is called synchronously using raise() on a normal execution path is an undefined behavior, the marking of raise() with __leaf__ makes raise() non-compliant with the C standard. > May I know your opinion, please? Thank you for your patience. -- Best regards, Tadeus ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: raise() marked __leaf__ is not C-compliant? 2020-10-28 7:33 ` Tadeus Prastowo @ 2020-10-28 11:53 ` Adhemerval Zanella 2020-10-28 13:19 ` Tadeus Prastowo 0 siblings, 1 reply; 14+ messages in thread From: Adhemerval Zanella @ 2020-10-28 11:53 UTC (permalink / raw) To: Tadeus Prastowo; +Cc: libc-help, Florian Weimer On 28/10/2020 04:33, Tadeus Prastowo wrote: > Allow me to fix some mistakes in my previous e-mails: > > On Wed, Oct 28, 2020 at 7:13 AM Tadeus Prastowo <0x66726565@gmail.com> wrote: >> >> Sorry that I closed this thread too soon and reopen it again now after >> re-reading GCC documentation because the GCC documentation thinks that >> raise() should not be marked __leaf__. >> >> On Wed, Oct 28, 2020 at 6:47 AM Tadeus Prastowo <0x66726565@gmail.com> wrote: >>> >>> On Tue, Oct 27, 2020 at 7:50 PM Adhemerval Zanella >>> <adhemerval.zanella@linaro.org> wrote: >>>> >>>> On 27/10/2020 13:57, Tadeus Prastowo via Libc-help wrote: >>>>> >>>>> My understanding is that if my single-threaded program installs a >>>>> signal handler using signal() and the handler is executed as a result >>>>> of calling raise(), then the handler has a defined behavior when the >>>>> handler accesses any object with static storage duration even though >>>>> the object is not qualified using volatile and not of type >>>>> sig_atomic_t. >>>>> >>>>> If my understanding is incorrect, I would like to have some pointer to >>>>> parts of the C standard that say so. >>>> >>>> Unfortunately this is not fully correct, specially if you accessing the >>>> object outside the signal handler. As you have noticed in your example, >>>> compiler can assume the variable 'terminated' won't be modified outside >>>> the function and apply optimization that avoid read its value from >>>> memory (most likely GCC will optimize out the '!terminated'). >>> >>> However, the compiler cannot assume that the variable `terminated' >>> won't be modified outside the loop if the loop calls a function whose >>> definition the compiler does not see (e.g., the raise() function >>> defined by glibc). Otherwise, the compiler is wrong if the loop calls >>> a function defined in another translation unit because the function >>> may modify `terminated'. >>> >>>> One of the best didactic explanation I have for this is from [1]. >>>> >>>> [1] https://wiki.sei.cmu.edu/confluence/display/c/SIG31-C.+Do+not+access+shared+objects+in+signal+handlers >>> >>> Thank you for the reference. >>> >>> I have studied it but found no pointer to parts of the C standard that >>> says that it is an undefined behavior to call a signal handler >>> synchronously using raise() on a normal execution path (i.e., not from >>> within another signal handler, which certainly is undefined behavior >>> as noted in "undefined behavior 131"). > > I meant to say: I have studied it but found no pointer to parts of the > C standard that 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 using raise() on a normal > execution path (i.e., the note "undefined behavior 131" only talks > about an undefined behavior that results from calling raise() from > within a signal handler). > >>> I know that the safest thing to do is always to qualify such a >>> variable with `volatile', but I think it is valid to not do so for the >>> cases that are not stated as >>> undefined/unspecified/implementation-defined behavior by the C >>> standard. > >>> That is why I ask for a pointer to parts of the C standard >>> if indeed calling a signal handler synchronously using raise() on a >>> normal execution path is an undefined behavior. > > I meant to say: That is why I ask for a pointer to parts of the C > standard if indeed accessing a non-volatile object with static storage > duration from within a signal handler that is called synchronously > using raise() on a normal execution path is an undefined behavior. > >>> After further thought, I found out that the problem is not glibc-2.30 >>> marking raise() with __leaf__ but the file-scope limitation of >>> `terminated'. As I already mentioned, since `terminated' has a >>> file-scope, the compiler can assume that `terminated' will not be >>> modified by a function defined in another translation unit. And >>> hence, the compiler is correct in optimizing out the `!terminated'. >>> Once `terminated' has a global scope, the compiler correctly does not >>> optimize it out as exemplified by the attached program. So, marking >>> raise() with __leaf__ does not make raise() non-compliant with the C >>> standard. >> >> After re-reading the GCC documentation, I think marking raise() with >> __leaf__ makes raise() non-compliant with the C standard, unless the C >> standard says that calling a signal handler synchronously using >> raise() on a normal execution path is an undefined behavior. > > I meant to say: After re-reading the GCC documentation, I think > marking raise() with __leaf__ makes raise() non-compliant with the C > standard, unless the C standard says that accessing a non-volatile > object with static storage duration from within a signal handler that > is called synchronously using raise() on a normal execution path is an > undefined behavior. > >> The GCC documentation on __leaf__ >> (https://gcc.gnu.org/onlinedocs/gcc-9.3.0/gcc/Common-Function-Attributes.html#Common-Function-Attributes) >> says (words in square brackets are added to fit our context): >> >> Note that leaf functions might indirectly run a signal handler defined >> in the current compilation unit that uses static variables. [...] >> There is no standard-compliant way to write such [an] implementation >> function [, for example, glibc-2.30 raise()], and the best that you >> can do is to remove the leaf attribute or mark all such static >> variables volatile. >> >> End quote. >> >> Glibc-2.30 raise() definitely runs a signal handler, and the signal >> handler can be defined in the current compilation unit using static >> variables. So, unless the C standard says that calling a signal >> handler synchronously using raise() on a normal execution path is an >> undefined behavior, the marking of raise() with __leaf__ makes raise() >> non-compliant with the C standard. > > I meant to say: Glibc-2.30 raise() definitely runs a signal handler, > and the signal handler can be defined in the current compilation unit > to use static variables. So, unless the C standard says that > accessing a non-volatile object with static storage duration from > within a signal handler that is called synchronously using raise() on > a normal execution path is an undefined behavior, the marking of > raise() with __leaf__ makes raise() non-compliant with the C standard. > >> May I know your opinion, please? The sentence "raise() definitely runs a signal handler" is not really valid in a portable sense. Afaik neither C nor POSIX states which signals should be delivered synchronously or asynchronously (although some do only make sense to be delivered synchronously such as SIGSEGV). However, Linux does ran some signals synchronously and I agree that using leaf attribute is incorrect and lead to this kind of problems. My point is to be fully portable, you need to assume any signal might be delivered asynchronously (and C standard specifies the volatile sig_atomic_t for such cases). Florian, what other leaf functions do you think are problematic? I am wondering if you would be better to just avoid this micro-optimization altogether. ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: raise() marked __leaf__ is not C-compliant? 2020-10-28 11:53 ` Adhemerval Zanella @ 2020-10-28 13:19 ` Tadeus Prastowo 2020-10-28 17:34 ` Adhemerval Zanella 0 siblings, 1 reply; 14+ messages in thread From: Tadeus Prastowo @ 2020-10-28 13:19 UTC (permalink / raw) To: Adhemerval Zanella; +Cc: libc-help, Florian Weimer On Wed, Oct 28, 2020 at 12:53 PM Adhemerval Zanella <adhemerval.zanella@linaro.org> wrote: > > On 28/10/2020 04:33, Tadeus Prastowo wrote: > > > > I meant to say: Glibc-2.30 raise() definitely runs a signal handler, > > and the signal handler can be defined in the current compilation unit > > to use static variables. So, unless the C standard says that > > accessing a non-volatile object with static storage duration from > > within a signal handler that is called synchronously using raise() on > > a normal execution path is an undefined behavior, the marking of > > raise() with __leaf__ makes raise() non-compliant with the C standard. > > > >> May I know your opinion, please? > > The sentence "raise() definitely runs a signal handler" is not really > valid in a portable sense. Afaik neither C nor POSIX states which signals > should be delivered synchronously or asynchronously (although some do > only make sense to be delivered synchronously such as SIGSEGV). > > However, Linux does ran some signals synchronously and I agree that using > leaf attribute is incorrect and lead to this kind of problems. My point is > to be fully portable, you need to assume any signal might be delivered > asynchronously (and C standard specifies the volatile sig_atomic_t for > such cases). Thank you for your response. 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? [1] Page 195 of https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf [2] The raise section of Chapter 3 "System Interfaces" of IEEE Standard for Information Technology---POSIX, Volume 2 "System Interfaces", Issue 7. -- Best regards, Tadeus ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: raise() marked __leaf__ is not C-compliant? 2020-10-28 13:19 ` Tadeus Prastowo @ 2020-10-28 17:34 ` Adhemerval Zanella 2020-10-28 19:23 ` Tadeus Prastowo 0 siblings, 1 reply; 14+ messages in thread From: Adhemerval Zanella @ 2020-10-28 17:34 UTC (permalink / raw) To: Tadeus Prastowo; +Cc: libc-help, Florian Weimer On 28/10/2020 10:19, Tadeus Prastowo wrote: > On Wed, Oct 28, 2020 at 12:53 PM Adhemerval Zanella > <adhemerval.zanella@linaro.org> wrote: >> >> On 28/10/2020 04:33, Tadeus Prastowo wrote: >>> >>> I meant to say: Glibc-2.30 raise() definitely runs a signal handler, >>> and the signal handler can be defined in the current compilation unit >>> to use static variables. So, unless the C standard says that >>> accessing a non-volatile object with static storage duration from >>> within a signal handler that is called synchronously using raise() on >>> a normal execution path is an undefined behavior, the marking of >>> raise() with __leaf__ makes raise() non-compliant with the C standard. >>> >>>> May I know your opinion, please? >> >> The sentence "raise() definitely runs a signal handler" is not really >> valid in a portable sense. Afaik neither C nor POSIX states which signals >> should be delivered synchronously or asynchronously (although some do >> only make sense to be delivered synchronously such as SIGSEGV). >> >> However, Linux does ran some signals synchronously and I agree that using >> leaf attribute is incorrect and lead to this kind of problems. My point is >> to be fully portable, you need to assume any signal might be delivered >> asynchronously (and C standard specifies the volatile sig_atomic_t for >> such cases). > > Thank you for your response. > > 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. > > [1] Page 195 of > https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf > [2] The raise section of Chapter 3 "System Interfaces" of IEEE > Standard for Information Technology---POSIX, Volume 2 "System > Interfaces", Issue 7. > ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: raise() marked __leaf__ is not C-compliant? 2020-10-28 17:34 ` Adhemerval Zanella @ 2020-10-28 19:23 ` Tadeus Prastowo 2020-10-28 20:17 ` Adhemerval Zanella 0 siblings, 1 reply; 14+ messages in thread From: Tadeus Prastowo @ 2020-10-28 19:23 UTC (permalink / raw) To: Adhemerval Zanella; +Cc: libc-help, Florian Weimer 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: > > On Wed, Oct 28, 2020 at 12:53 PM Adhemerval Zanella > > <adhemerval.zanella@linaro.org> wrote: > >> > >> The sentence "raise() definitely runs a signal handler" is not really > >> valid in a portable sense. Afaik neither C nor POSIX states which signals > >> should be delivered synchronously or asynchronously (although some do > >> only make sense to be delivered synchronously such as SIGSEGV). > >> > >> However, Linux does ran some signals synchronously and I agree that using > >> leaf attribute is incorrect and lead to this kind of problems. My point is > >> to be fully portable, you need to assume any signal might be delivered > >> asynchronously (and C standard specifies the volatile sig_atomic_t for > >> such cases). > > > > Thank you for your response. > > > > 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." 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. > > [1] Page 195 of > > https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf > > [2] The raise section of Chapter 3 "System Interfaces" of IEEE > > Standard for Information Technology---POSIX, Volume 2 "System > > Interfaces", Issue 7. Thank you for your patience in allowing me to make myself clear. -- Best regards, Tadeus ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: raise() marked __leaf__ is not C-compliant? 2020-10-28 19:23 ` Tadeus Prastowo @ 2020-10-28 20:17 ` Adhemerval Zanella 2020-10-29 7:50 ` Tadeus Prastowo 0 siblings, 1 reply; 14+ messages in thread From: Adhemerval Zanella @ 2020-10-28 20:17 UTC (permalink / raw) To: Tadeus Prastowo; +Cc: libc-help, Florian Weimer 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: >>> On Wed, Oct 28, 2020 at 12:53 PM Adhemerval Zanella >>> <adhemerval.zanella@linaro.org> wrote: >>>> >>>> The sentence "raise() definitely runs a signal handler" is not really >>>> valid in a portable sense. Afaik neither C nor POSIX states which signals >>>> should be delivered synchronously or asynchronously (although some do >>>> only make sense to be delivered synchronously such as SIGSEGV). >>>> >>>> However, Linux does ran some signals synchronously and I agree that using >>>> leaf attribute is incorrect and lead to this kind of problems. My point is >>>> to be fully portable, you need to assume any signal might be delivered >>>> asynchronously (and C standard specifies the volatile sig_atomic_t for >>>> such cases). >>> >>> Thank you for your response. >>> >>> 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). > > 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. I am not a language lawyer here, and I agree with you that adding leaf attribute on 'raise' is wrong. But because glibc can't guarantee how the underlying kernel will trigger the signal in a raise syscall. 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. ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: raise() marked __leaf__ is not C-compliant? 2020-10-28 20:17 ` Adhemerval Zanella @ 2020-10-29 7:50 ` Tadeus Prastowo 0 siblings, 0 replies; 14+ messages in thread From: Tadeus Prastowo @ 2020-10-29 7:50 UTC (permalink / raw) To: Adhemerval Zanella; +Cc: libc-help, Florian Weimer 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 ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: raise() marked __leaf__ is not C-compliant? 2020-10-27 16:57 raise() marked __leaf__ is not C-compliant? Tadeus Prastowo 2020-10-27 18:50 ` Adhemerval Zanella @ 2020-10-28 8:21 ` Florian Weimer 2020-10-28 12:58 ` Tadeus Prastowo 1 sibling, 1 reply; 14+ messages in thread From: Florian Weimer @ 2020-10-28 8:21 UTC (permalink / raw) To: Tadeus Prastowo via Libc-help * Tadeus Prastowo via Libc-help: > With glibc-2.30 raise() being marked __leaf__, the attached C program > has an undefined behavior because it terminates when compiled with GCC > using either -O0 or -Os but enters an infinite loop when using -O2. Yes, I think the leaf attribute is incorrect for raise and similar functions. throw()/noexcept is questionable as well because the GNU implementation supports throwing from a synchronous signal handler, as an extension. I can't find a Bugzilla bug for this right now, but I think we discussed it before. Thanks, Florian -- Red Hat GmbH, https://de.redhat.com/ , Registered seat: Grasbrunn, Commercial register: Amtsgericht Muenchen, HRB 153243, Managing Directors: Charles Cachera, Brian Klemm, Laurie Krebs, Michael O'Neill ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: raise() marked __leaf__ is not C-compliant? 2020-10-28 8:21 ` Florian Weimer @ 2020-10-28 12:58 ` Tadeus Prastowo 2020-10-28 17:16 ` Tadeus Prastowo 0 siblings, 1 reply; 14+ messages in thread From: Tadeus Prastowo @ 2020-10-28 12:58 UTC (permalink / raw) To: Florian Weimer; +Cc: Tadeus Prastowo via Libc-help On Wed, Oct 28, 2020 at 9:21 AM Florian Weimer <fweimer@redhat.com> wrote: > > * Tadeus Prastowo via Libc-help: > > > With glibc-2.30 raise() being marked __leaf__, the attached C program > > has an undefined behavior because it terminates when compiled with GCC > > using either -O0 or -Os but enters an infinite loop when using -O2. > > Yes, I think the leaf attribute is incorrect for raise and similar > functions. throw()/noexcept is questionable as well because the GNU > implementation supports throwing from a synchronous signal handler, as > an extension. Thank you for your response. > I can't find a Bugzilla bug for this right now, but I think we discussed > it before. I will open one. Thank you. > Thanks, > Florian -- Best regards, Tadeus ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: raise() marked __leaf__ is not C-compliant? 2020-10-28 12:58 ` Tadeus Prastowo @ 2020-10-28 17:16 ` Tadeus Prastowo 0 siblings, 0 replies; 14+ messages in thread From: Tadeus Prastowo @ 2020-10-28 17:16 UTC (permalink / raw) To: Florian Weimer; +Cc: Tadeus Prastowo via Libc-help On Wed, Oct 28, 2020 at 1:58 PM Tadeus Prastowo <0x66726565@gmail.com> wrote: > > On Wed, Oct 28, 2020 at 9:21 AM Florian Weimer <fweimer@redhat.com> wrote: > > > > * Tadeus Prastowo via Libc-help: > > > > > With glibc-2.30 raise() being marked __leaf__, the attached C program > > > has an undefined behavior because it terminates when compiled with GCC > > > using either -O0 or -Os but enters an infinite loop when using -O2. > > > > Yes, I think the leaf attribute is incorrect for raise and similar > > functions. throw()/noexcept is questionable as well because the GNU > > implementation supports throwing from a synchronous signal handler, as > > an extension. > > Thank you for your response. > > > I can't find a Bugzilla bug for this right now, but I think we discussed > > it before. > > I will open one. I have opened it: https://sourceware.org/bugzilla/show_bug.cgi?id=26802 > Thank you. > > > Thanks, > > Florian -- Best regards, Tadeus ^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2020-10-29 7:50 UTC | newest] Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2020-10-27 16:57 raise() marked __leaf__ is not C-compliant? Tadeus Prastowo 2020-10-27 18:50 ` Adhemerval Zanella 2020-10-28 5:47 ` Tadeus Prastowo 2020-10-28 6:13 ` Tadeus Prastowo 2020-10-28 7:33 ` Tadeus Prastowo 2020-10-28 11:53 ` Adhemerval Zanella 2020-10-28 13:19 ` Tadeus Prastowo 2020-10-28 17:34 ` Adhemerval Zanella 2020-10-28 19:23 ` Tadeus Prastowo 2020-10-28 20:17 ` Adhemerval Zanella 2020-10-29 7:50 ` Tadeus Prastowo 2020-10-28 8:21 ` Florian Weimer 2020-10-28 12:58 ` Tadeus Prastowo 2020-10-28 17:16 ` Tadeus Prastowo
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).