Bug 395109 - Infinite recursion during initialization when stdlib's atexit() allocates
Summary: Infinite recursion during initialization when stdlib's atexit() allocates
Status: RESOLVED FIXED
Alias: None
Product: Heaptrack
Classification: Applications
Component: general (show other bugs)
Version: 1.1.0
Platform: Other Linux
: NOR normal
Target Milestone: ---
Assignee: Milian Wolff
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2018-06-07 13:26 UTC by Daniel Vrátil
Modified: 2018-07-02 21:00 UTC (History)
0 users

See Also:
Latest Commit:
Version Fixed In:
Sentry Crash Report:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Daniel Vrátil 2018-06-07 13:26:10 UTC
Trying to use heaptrack with uClibc stdlib crashes immediately on start due to infinite recursion.

When a heaptrack's allocation function is called for the first time it calls ::hooks::init(), which then calls atexit(). If the atexit() implementation allocates then the allocation function in heaptrack is called again, which then calls ::hooks::init() again etc. etc. leading to an infinite recursion.


Calling heaptrack_init() before atexit() in ::hooks::init() solves the problem, but I'm not sure if it may have any side effects.

Note that even glibc calls memory allocation functions in its atexit() implementation. However, it has a preallocated static array for 32 exit handlers, so heaptrack forcing itself at the beginning of LD_PRELOAD is unlikely to hit the limit (which would force glibc to allocate another array). In case of uClibc it can be compiled with or without dynamic allocation in atexit(), ours is compiled with dynamic allocation enabled and as such it has no preallocated array.




Bit of a backtrace:

#1  0xb6cac238 in __new_exitfn () at libc/stdlib/_atexit.c:241
#2  0xb6cac098 in __GI___cxa_atexit (func=0xb6edf640 <(anonymous namespace)::hooks::<lambda()>::_FUN(void)>, arg=0x0, dso_handle=0xb6ef6338 <_dl_getenv+88>) at libc/stdlib/_atexit.c:164
#3  0xb6edf694 in (anonymous namespace)::hooks::init () at /sources/heaptrack-1.1.0/src/track/heaptrack_preload.cpp:126
#4  0xb6edfaf8 in realloc (ptr=0x0, size=320) at /sources/heaptrack-1.1.0/src/track/heaptrack_preload.cpp:198
#5  0xb6cac298 in __new_exitfn () at libc/stdlib/_atexit.c:246
#6  0xb6cac098 in __GI___cxa_atexit (func=0xb6edf640 <(anonymous namespace)::hooks::<lambda()>::_FUN(void)>, arg=0x0, dso_handle=0xb6ef6338 <_dl_getenv+88>) at libc/stdlib/_atexit.c:164
#7  0xb6edf694 in (anonymous namespace)::hooks::init () at /sources/heaptrack-1.1.0/src/track/heaptrack_preload.cpp:126
#8  0xb6edfaf8 in realloc (ptr=0x0, size=320) at /sources/heaptrack-1.1.0/src/track/heaptrack_preload.cpp:198
#9  0xb6cac298 in __new_exitfn () at libc/stdlib/_atexit.c:246
#10 0xb6cac098 in __GI___cxa_atexit (func=0xb6edf640 <(anonymous namespace)::hooks::<lambda()>::_FUN(void)>, arg=0x0, dso_handle=0xb6ef6338 <_dl_getenv+88>) at libc/stdlib/_atexit.c:164
#11 0xb6edf694 in (anonymous namespace)::hooks::init () at /sources/heaptrack-1.1.0/src/track/heaptrack_preload.cpp:126
#12 0xb6edfaf8 in realloc (ptr=0x0, size=320) at /sources/heaptrack-1.1.0/src/track/heaptrack_preload.cpp:198
#13 0xb6cac298 in __new_exitfn () at libc/stdlib/_atexit.c:246
#14 0xb6cac098 in __GI___cxa_atexit (func=0xb6edf640 <(anonymous namespace)::hooks::<lambda()>::_FUN(void)>, arg=0x0, dso_handle=0xb6ef6338 <_dl_getenv+88>) at libc/stdlib/_atexit.c:164
#15 0xb6edf694 in (anonymous namespace)::hooks::init () at /sources/heaptrack-1.1.0/src/track/heaptrack_preload.cpp:126
#16 0xb6edfaf8 in realloc (ptr=0x0, size=320) at /sources/heaptrack-1.1.0/src/track/heaptrack_preload.cpp:198
Comment 1 Milian Wolff 2018-06-12 20:58:52 UTC
Hey Dan,

thanks for the report. Can you tell me how to reproduce this locally on Arch? Without that, it's going to be hard for me to fix this properly.

Have you tried moving the atexit call into the lambda of the heaptrack_init call? I.e. after the comment

// cleanup environment to prevent tracing of child apps

If that works for you too, then I think it would be preferrable.
Comment 2 Daniel Vrátil 2018-07-02 09:31:05 UTC
Hi Milian, sorry for the slow response.

You probably have to compile uClibc (I'm running against an ancient one here, 0.9.33.2) and then LD_PRELOAD it or link an app directly against in. When you run the app under heaptrack, you should get a crash immediately at the start. I don't know if there's an easy way to reproduce this against glibc.

Moving the atexit() at the end of the lambda works.
Comment 3 Milian Wolff 2018-07-02 21:00:22 UTC
Git commit 5ff967ab12fda20834100ea3861d65d74221db89 by Milian Wolff.
Committed on 02/07/2018 at 20:58.
Pushed by mwolff into branch '1.1'.

Call libc / libstdc++ freeres function in libheaptrack's atexit

On one hand, this prevents an infinite recursion when the call to
atexit allocates, which happens e.g. with uClibc, since the
call from heaptrack_preload did not install the recursion guard yet.

On the other hand, this code move ensures that the freeres functions
get called properly before heaptrack_stop is called.

M  +0    -19   src/track/heaptrack_preload.cpp
M  +19   -0    src/track/libheaptrack.cpp

https://commits.kde.org/heaptrack/5ff967ab12fda20834100ea3861d65d74221db89