ARM Unwinding

Recently, I’ve been writing some C++ ARM code and been checking the output of GCC.  After writing a bit of code I found that the ELF suddenly had a load of extra symbols and the .ARM.extab section was created.  I did a lot of Googling around and no one had a good explanation, so I thought I’d write up my experience.

What is the problem?

I had a bit of code like this:

template<typename T>
class TypeWrapper
{
  public:
    TypeWrapper(const T& object) :
        m_object(object)
    { }

    ~TypeWrapper()
    { }

  private:
    T m_object;
};

using Type = TypeWrapper<char>;

namespace {
    Type a = 1;
}

The problem occurred because each of these three parts were in separate parts of the source. When they’re put together like this it’s much easier to see the problem, but is not exactly clear still.
With the ~TypeWrapper() in the code all of the results described at the start of this post occur. Removing the destructor drops them.

Diagnosing the issue

It took me a while to narrow it down to the destructor… Mostly by deleting every function in the class and adding them back one by one. It sounds easy, but the example above is far simpler than the case I had. After finding it was the destructor I tried every attribute I could think of, noexcept(true), noexcept(false), throws(). I found the -fno-unwind-tables and -fno-asynchronous-unwind-tables flags for GCC and tried them for compiling the objects and for the linker.
Finally, I came across a linker flag which provided some insight into the issue, -Wl,-trace,-debug. Using this flag I found that when the destructor is explicitly defined it outputs the following extra lines at the link stage:

libstdc++.a(atexit_arm.o)
libgcc.a(unwind-arm.o)
libgcc.a(libunwind.o)
libgcc.a(pr-support.o)

Now it looks like the atexit_arm object is pulling in the unwind-arm etc. That’s a good start, it’s because we’re calling atexit, but I haven’t called that function anywhere, simply declared an empty destructor…

Why is atexit being linked?

So if you’re looking at the block of code above you might be able to see the issue much faster than when it was spread across a 5000 line code base. However with the code block above we can see the problem if we understand how C++ works.
The problem occurs because we have a static constructed type. Before a static type is used for the first time it must be constructed and the compiler ensures that this occurs. It also ensures that the static variable is deconstructed before the program terminates. In order to do this GCC uses the atexit function to call the destructor.
An interesting side-note here is that having no destructor acts differently to having a default destructor. What I mean by that is that the following are not considered the same for the purposes of static classes:

class Class
{
  public:
    ~Class() = default;
};
class Class
{ };

In the first case, the explicit definition of the default destructor makes GCC create the atexit call when a static instance of the class is created.

So, what can I do about it?

Well, the most obvious thing is to simply not make a static variable of a class type that has a destructor. In fact, not having static variables is generally considered a good thing especially if you want to perform any kind of unit testing.

But I REALLY need to because <insert excuse here>… Well, I’m not sure I should really tell you this, but you’re an adult (probably) so do with it what you will. Just remember, you’re using a hack that might not work forever and you can probably re-factor your code so you don’t need it.

The solution lies with what symbols atexit is pulling in. For some reason, which I’ve not bothered to look into for the reasoning in the previous paragraph, this function pulls in the symbol __aeabi_unwind_cpp_pr0 which pulls in all the unwinding stuff. So, if we provide that symbol ourselves, then it won’t be pulled in from libunwind. Add the following to one of your translation units with a big comment explaining why you’re being a very naughty software engineer:

extern "C" void __aeabi_unwind_cpp_pr0(){}

Hold on a minute, I don’t have a static instance!

So you reached the end of the post and didn’t find what you were after, sad times ?. You have a destructor causing the exact same issues, but atexit isn’t there. Well don’t be gloomy… I bet you have a virtual destructor. Yep, the exact same issue occurs there too, but for a slightly different reason.
If we look back at our linker trace earlier, we see del_op.o being pulled in if we use a virtual destructor.

class Class
{
  public:
    virtual ~Class() = default;
};
libstdc++.a(del_op.o)
libgcc.a(unwind-arm.o)
libgcc.a(libunwind.o)
libgcc.a(pr-support.o)

The same symptom, different reason. It can be fixed in exactly the same way as previously, but it’s still just a big hack. There’s got to be a better way to fix it. Well, not that I can see… but there is a less compiler/library specific way to fix it: don’t have a virtual destructor.
That’s all well and good I hear you say, but I need to clean up my class! Yes, you do… just do it manually instead. Every time your class instance goes out of scope make sure you call a virtual method that cleans up the class, just as if you had a destructor. This is incredibly error prone however, so do so at your peril.
Your class has static resources though with non-virtual destructors. How can they be cleaned up? Simply call their destructors directly, C++ permits you to do so. If you do it more than once though you could have an issue, so make sure the class that does so can only ever be destroyed through its base pointer, otherwise the code might end up with a double destruction.

Summary

GCC pulls in symbols at unexpected times for seemingly no good reason. You can work around this by structuring your code better in the case of static instances. However, with virtual destructors you’re a bit stuffed and have to resort to weird hacks.

Leave a Reply

Your email address will not be published. Required fields are marked *