-fno-use-cxa-atexit

Bug #1586114 reported by Liviu Ionescu
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
GNU Arm Embedded Toolchain
Invalid
Undecided
Liviu Ionescu

Bug Description

The GCC manual states:

-fuse-cxa-atexit
Register destructors for objects with static storage duration with the __cxa_atexit function rather than the atexit function.

The actual behaviour is correct, with the small notice that, in the ARM compiler case, when -fuse-cxa-atexit is requested, not __cxa_atexit but _aeabi_atexit() is used for all static destructors, either global variables or static variables inside a function.

When -fno-use-cxa-atexit is requested, the ARM compiler behaves somehow strangely and unexpected, for global variables generates code for the .fini section (which is good) but for static variables it calls atexit().

For example something like:

---
class C; // ...

C c1;

void f(void)
{
  static C c2;
  // ...
}
---

generates atexit only for c2:

---
void f(void)
{
 800172c: b508 push {r3, lr}
  static C c;
 800172e: 4b06 ldr r3, [pc, #24] ; (8001748 <f()+0x1c>)
 8001730: 681b ldr r3, [r3, #0]
 8001732: f013 0f01 tst.w r3, #1
 8001736: d105 bne.n 8001744 <f()+0x18>
 8001738: 2201 movs r2, #1
 800173a: 4b03 ldr r3, [pc, #12] ; (8001748 <f()+0x1c>)
 800173c: 601a str r2, [r3, #0]
 800173e: 4803 ldr r0, [pc, #12] ; (800174c <f()+0x20>)
 8001740: f000 f84a bl 80017d8 <atexit>
 8001744: bd08 pop {r3, pc}
 8001746: bf00 nop
 8001748: 20000224 .word 0x20000224
 800174c: 0800169d .word 0x0800169d
---

The problem is that on tight embedded systems, atexit() might not be available, since normally it requires dynamically allocated array of pointers, and the use of malloc() sometimes is prohibited.

My expectation was that once I enable -fno-use-cxa-atexit, the behaviour is consistent, with all static destructors orderly executed via the .fini section, and no atexit() calls be generated.

Could you check what should be the correct behaviour?

Thank you,

Liviu

Revision history for this message
Liviu Ionescu (ilg) wrote :

another problem with the approach of having part of the destructors executed by .fini and part by atexit() is that the expected order ("the reverse order of the constructors") cannot be guaranteed, the exit() procedure usually executes the atexit() array and then the .fini array().

Revision history for this message
Andre Vieira (andre-simoesdiasvieira) wrote :

Hi Liviu,

As you point out the gcc documentation states:

-fuse-cxa-atexit
    Register destructors for objects with static storage duration with the __cxa_atexit function rather than the atexit function. This option is required for fully standards-compliant handling of static destructors, but only works if your C library supports __cxa_atexit.

So I would expect -fno-use-cxa-atexit to use the 'atexit' function as it does.

Also, how would you want the .fini section to be used here? You should only call the destructor for an object that has been constructed. So for static objects in functions, you should only call their destructor if the function has been called at least once. You need to keep track of that.

Thats exactly what the following sequence does:
 800172e: 4b06 ldr r3, [pc, #24] ; (8001748 <f()+0x1c>)
 8001730: 681b ldr r3, [r3, #0]
 8001732: f013 0f01 tst.w r3, #1
 8001736: d105 bne.n 8001744 <f()+0x18>
 8001738: 2201 movs r2, #1
 800173a: 4b03 ldr r3, [pc, #12] ; (8001748 <f()+0x1c>)
 800173c: 601a str r2, [r3, #0]
 800173e: 4803 ldr r0, [pc, #12] ; (800174c <f()+0x20>)
 8001740: f000 f84a bl 80017d8 <atexit>

The first time this function is called, it will register it and make sure that atexit calls its destructor.

Now I have not looked any further into this, but I believe the global objects are created first. The static objects within functions are constructed upon entering the function they reside in for the first time. And as you said the atexit array is executed before the .fini array, so the static objects are destructed before the global objects. So that sounds OK to me. Do please correct me if I'm wrong.

Cheers,
Andre

Revision history for this message
Liviu Ionescu (ilg) wrote :

> Also, how would you want the .fini section to be used here? You should only call the destructor for an object that has been constructed. So for static objects in functions, you should only call their destructor if the function has been called at least once. You need to keep track of that.

I thought that the same mechanism that prevents a constructor to be called twice can also be used to enable the destructor.

but you are right, the dynamic nature of the static objects in functions, constructed if the function was called once, prevents the use of .fini, which is fully static.

I don't know what to say, probably the only way to avoid the dynamic memory allocations in atexit() is to use -fno-use-cxa-atexit for the static global objects and to avoid function static objects at all.

Liviu Ionescu (ilg)
Changed in gcc-arm-embedded:
status: New → Invalid
assignee: nobody → Liviu Ionescu (ilg)
Revision history for this message
Thomas Preud'homme (thomas-preudhomme) wrote :

Hi Liviu,

Did you mean use -fuse-cxa-atexit instead? -fuse-cxa-atexit will register destructor for global objects in __cxa_atexit so -fno-use-cxa-atexit will do the reverse, ie use atexit function.

Best regards.

Revision history for this message
Liviu Ionescu (ilg) wrote :

With the current implementation -fuse-cxa-atexit will always use _aeabi_atexit(), while -fno-use-cxa-atexit will use .fini for static global destructors and atexit() for static destructors in functions.

So for embedded apps I prefer -fno-use-cxa-atexit.

To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.