[llvm-dev] Issue with __attribute__((constructor)) and -Os -fno-common

Fangrui Song via llvm-dev llvm-dev at lists.llvm.org
Sun Jun 14 12:18:48 PDT 2020


On 2020-06-12, Mehdi AMINI via llvm-dev wrote:
>On Fri, Jun 12, 2020 at 1:05 AM Jerome Forissier via llvm-dev <
>llvm-dev at lists.llvm.org> wrote:
>
>>
>>
>> On 6/11/20 11:25 PM, James Y Knight wrote:
>> > The global constructor was removed by setting the initial value of "val"
>> to
>> > 1 instead of 0. So, the behavior of this program is preserved. Doesn't
>> look
>> > like erroneous behavior.
>>
>> OK, my example is too simplified indeed. Please consider the following
>> instead:
>>
>> int val;
>>
>> static void __attribute__((constructor)) init_fn(void)
>> {
>>         val++;
>> }
>>
>> int main(int argc, char *argv[])
>> {
>>         return val;
>> }
>>
>> With this, clang -Os -fno-common generates a global variable initialized
>> to 1 and discards init_fn().
>>
>> Now, what happens if the executable is later linked against a shared
>> library which has its own constructor and sets "val" to some non-zero
>> value? I mean this for instance:
>>
>> extern int val;
>>
>> static void __attribute__((constructor)) so_init_fn(void)
>> {
>>         val = 1;
>> }
>>
>> I would expect the main program to return 2, not 1.
>>
>
>It seems to me that there is no ordering guarantee that your so_init_fn()
>would run before init_fn(), isn't this a case of "static initialization
>order fiasco"?
><https://www.google.com/search?client=safari&rls=en&q=static+initialization+order+fiasco&ie=UTF-8&oe=UTF-8>

I tend to agree that clang has a bug.

__attribute__((constructor)) is an extension to the C++ standard.
Even if C++ defined this and said this were an undefined behavior,
an implementation can augment the C++ standard by providing a definition
of an undefined behavior.

On ELF, the implementation uses Initialization and Termination
Functions, which are subject to
http://www.sco.com/developers/gabi/latest/ch5.dynamic.html#init_fini

"Before the initialization functions for any object A is called, the
initialization functions for any other objects that object A depends on
are called." The ld.so implementations use a post-order traversal of DT_NEEDED
dependencies.
I tend to think the intention of the function attribute
(https://gcc.gnu.org/onlinedocs/gcc-10.1.0/gcc/Common-Function-Attributes.html#Common-Function-Attributes)
is to conform to the ELF specification. This is a property users can
reliably depend on.

In Jerome's example, it is guaranteed that the initialization function
in the shared object runs before the one in the main executable.
clang should not optimize out the initialization function to alter the
observed program behavior.

>>
>> Last thing, if I define "val" as volatile, the programs behaves as
>> expected.
>>
>> Are we in "unspecified, just don't do this" territory here?
>>
>> Thanks,
>> --
>> Jerome
>>
>> >
>> > On Thu, Jun 11, 2020 at 10:12 AM Jerome Forissier via llvm-dev <
>> > llvm-dev at lists.llvm.org> wrote:
>> >
>> >> Hi,
>> >>
>> >> I think that Clang erroneously discards a function annotated with
>> >> __attribute__((constructor)) when flags -Os -fno-common are given. Test
>> >> case below.
>> >>
>> >> What do you think?
>> >>
>> >> Thanks.
>> >>
>> >> ----8<--------8<--------8<--------8<--------8<--------8<--------
>> >> $ cat ctor.c
>> >> int val;
>> >>
>> >> static void __attribute__((constructor)) init_fn(void)
>> >> {
>> >>         val = 1;
>> >> }
>> >>
>> >> int main(int argc, char *argv[])
>> >> {
>> >>         return val;
>> >> }
>> >> ----8<--------8<--------8<--------8<--------8<--------8<--------
>> >>
>> >> Here is what I observed:
>> >>
>> >> - Clang (10.0.0-4ubuntu1) with -Os -fno-common: function init_fn() is
>> >> NOT emitted,
>> >> - Clang (10.0.0-4ubuntu1) with no flag, or only -Os or -fno-common:
>> >> init_fn() is present as expected,
>> >> - GCC (Ubuntu 9.3.0-10ubuntu1) with the same flags: init_fn() is present
>> >> too,
>> >> - Since https://reviews.llvm.org/D75056, -fno-common is the default and
>> >> therefore -Os is enough to cause the issue.
>> >>
>> >> ----8<--------8<--------8<--------8<--------8<--------8<--------
>> >> $ clang --target=arm-linux-gnueabihf -Os -fno-common -S ctor.c \
>> >>   -o /dev/stdout | grep init_fn
>> >> $ clang --target=arm-linux-gnueabihf -Os -S ctor.c \
>> >>   -o /dev/stdout | grep init_fn
>> >>         .p2align        2               @ -- Begin function init_fn
>> >>         .type   init_fn,%function
>> >>         .code   32                      @ @init_fn
>> >> init_fn:
>> >>         .size   init_fn, .Lfunc_end0-init_fn
>> >>         .long   init_fn(target1)
>> >>         .addrsig_sym init_fn
>> >> $ clang --target=arm-linux-gnueabihf -fno-common -S ctor.c \
>> >>   -o /dev/stdout | grep init_fn
>> >>         .p2align        2               @ -- Begin function init_fn
>> >>         .type   init_fn,%function
>> >>         .code   32                      @ @init_fn
>> >> init_fn:
>> >>         .size   init_fn, .Lfunc_end0-init_fn
>> >>         .long   init_fn(target1)
>> >>         .addrsig_sym init_fn
>> >> $ arm-linux-gnueabihf-gcc -Os -fno-common -S ctor.c \
>> >>   -o /dev/stdout | grep init_fn
>> >>         .type   init_fn, %function
>> >> init_fn:
>> >>         .size   init_fn, .-init_fn
>> >>         .word   init_fn(target1)
>> >> ----8<--------8<--------8<--------8<--------8<--------8<--------
>> >>
>> >> --
>> >> Jerome
>> >> _______________________________________________
>> >> LLVM Developers mailing list
>> >> llvm-dev at lists.llvm.org
>> >> https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev
>> >>
>> >
>> _______________________________________________
>> LLVM Developers mailing list
>> llvm-dev at lists.llvm.org
>> https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev
>>

>_______________________________________________
>LLVM Developers mailing list
>llvm-dev at lists.llvm.org
>https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev



More information about the llvm-dev mailing list