3
votes

I'm about to remove "as many as possible" dynamic heap allocation in my application and I wonder how I can make sure I didn't miss anything.

Currently I'm looking for a way to easily or even automatically tell, if any (or which) parts of the code might invoke the standard implementations of new/delete or malloc/free without having to dynamically trace allocations (i.e. via static code analysis or feedback from compiler/linker).

It's easy to spot (or search for) code which directly calls new or malloc of course:

int main() {
  auto s = new std::string();
  delete s;
}

Just in case the allocations are hidden deep in a 3rd party library or in cases which are less obvious (like throw) I can still search for the mangled symbols for new/delete in my binary:

g++ main.cpp
nm a.out | grep -E "_Znwm|_Znam|_Znwj|_Znaj|_ZdlPv|_ZdaPv|malloc|free"
                 U _ZdlPvm@@CXXABI_1.3.9
                 U _Znwm@@GLIBCXX_3.4

But this approach will only find direct uses of new/delete/malloc/free. In case my code (or 3rd party stuff) makes use of the standard library you won't detect it by just calling nm:

int main() {
    std::string a;
    for(int i = 0; i < 100; ++i) {
        a += "data ";
    }
}

My current approach is to link against static standard libraries (-static-libgcc / -static-libstdc++) and look if there's a reference to new/delete at all in the whole binary:

g++ -static-libgcc -static-libstdc++ main.cpp
nm a.out | grep -E "_Znwm|_Znam|_Znwj|_Znaj|_ZdlPv|_ZdaPv|malloc|free"
0000000000471fd0 T __cxa_free_dependent_exception
0000000000471f30 T __cxa_free_exception
                 U free@@GLIBC_2.2.5
                 U __freelocale@@GLIBC_2.2.5
                 U malloc@@GLIBC_2.2.5
0000000000471b20 T _ZdlPv
0000000000491bf0 T _ZdlPvm
0000000000471bc0 t _ZN12_GLOBAL__N_14pool4freeEPv.constprop.2
0000000000402a20 t _ZN12_GLOBAL__N_14pool4freeEPv.constprop.2.cold.5
0000000000471e80 T _ZN9__gnu_cxx9__freeresEv
0000000000472240 T _Znwm
0000000000491c00 T _ZnwmRKSt9nothrow_t
0000000000403f37 t _ZnwmRKSt9nothrow_t.cold.0

This approach works for small binaries where you can manage to have zero heap allocations but as soon as you want to allow some allocations (e.g. in code which gets executed only once or in error cases it becomes very difficult to consequently separate code which might allocate heap memory and the stuff which won't.

In the best scenario I can currently imagine the compiler or a static code analyzer provides me with a list of locations in my source code which could result in dynamic heap allocations. This list I could regularly check/filter for cases which are OK in my setup (e.g. bootstrap code or error handling) and those where I have to refactor (e.g. by providing a special allocator).

What are your approaches, tools, experiences?

1
You really need to bound the problem somehow. Finding use of dynamic memory allocation in third-party libraries is a tough ask, give the range of implementation choices and potential for the library to be updated outside your control. Generally it is easier to design a program from the ground up to avoid dynamic memory allocation (or, at least, limit usage to some specific code) than it is to find instances in a non-trivial existing program and remove them.Peter
This is a pain to find. What I have done in the past is to completely eradicate the .heap segment from the linker files. If something screams at linker stage after that, there's some hidden heap code in some library. The long term solution is to port to C, since it is more suitable for embedded programming.Lundin

1 Answers

2
votes

One strategy might be to wrap the calls to malloc/calloc with your own function which decides if they are allowed and asserts (or logs) if not. The decision can be made by checking a global flag which you set/clear during initialization, error handlers, etc.

This is, of course, not static, but it might serve as a good starting point for sanitizing your code.