1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 //    names, trademarks, service marks, or product names of the Licensor
11 //    and its affiliates, except as required to comply with Section 4(c) of
12 //    the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 //     http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 
25 #include "pxr/pxr.h"
26 #include "pxr/base/arch/mallocHook.h"
27 #include "pxr/base/arch/attributes.h"
28 #include "pxr/base/arch/defines.h"
29 #include "pxr/base/arch/env.h"
30 
31 #if !defined(ARCH_OS_WINDOWS)
32 #   include <dlfcn.h>
33 #endif
34 #include <cstring>
35 
36 #if defined(ARCH_OS_DARWIN)
37 #   include <sys/malloc.h>
38 #elif defined(__DragonFly__)
39 #   include <stdlib.h>
40 #else
41 #   include <malloc.h>
42 #endif /* defined(ARCH_OS_DARWIN) */
43 
44 #if !defined(__MALLOC_HOOK_VOLATILE)
45 #   define __MALLOC_HOOK_VOLATILE
46 #endif /* !defined(__MALLOC_HOOK_VOLATILE) */
47 
48 using std::string;
49 
50 /*
51  * These are hook variables (they're not functions, so they don't need
52  * an extern "C"). Allocator libraries must provide these hooks in order for
53  * ArchMallocHook to work.
54  */
55 extern void* (*__MALLOC_HOOK_VOLATILE __malloc_hook)(size_t __size,  const void*);
56 extern void* (*__MALLOC_HOOK_VOLATILE __realloc_hook)(void* __ptr, size_t __size, const void*);
57 extern void* (*__MALLOC_HOOK_VOLATILE __memalign_hook)(size_t __alignment, size_t __size, const void*);
58 extern void (*__MALLOC_HOOK_VOLATILE __free_hook)(void* __ptr,  const void*);
59 
60 PXR_NAMESPACE_OPEN_SCOPE
61 
62 /*
63  * ArchMallocHook requires allocators to provide some specific functionality
64  * in order to work properly. Allocators must provide hooks that are executed
65  * when allocation functions are called (see above) and a corresponding set of
66  * functions that do not execute hooks at all.
67  *
68  * As of this writing, we have two allocator libraries that have been custom
69  * modified to meet these requirements: ptmalloc3 and jemalloc. Code below
70  * explicitly looks for one of these two libraries.
71  *
72  * If your program doesn't link against these, you could still do everything
73  * we're doing with the regular built-in malloc of glibc, but it would require
74  * you to LD_PRELOAD an additional library that knew how to work with standard
75  * malloc --- but in that case, you might just as well LD_PRELOAD ptmalloc3
76  * itself.
77  *
78  * So, to boil this down: if you link against or LD_PRELOAD the listed
79  * libs, the code in this file will be enabled.  Otherwise, it'll
80  * run-time detect that it can't do what it wants.
81  *
82  * Note that support for non-linux and non-64 bit platforms is not provided.
83  */
84 
85 // Helper function that returns true if "malloc" is provided by the same
86 // library as the given function. This is needed to determine which allocator
87 // is active; being able to find a particular library's malloc function doesn't
88 // ensure that library is the active allocator.
89 static bool
_MallocProvidedBySameLibraryAs(const char * functionName,bool skipMallocCheck)90 _MallocProvidedBySameLibraryAs(const char* functionName,
91                                bool skipMallocCheck)
92 {
93 #if !defined(ARCH_OS_WINDOWS)
94     const void* function = dlsym(RTLD_DEFAULT, functionName);
95     if (!function) {
96         return false;
97     }
98 
99     Dl_info functionInfo, mallocInfo;
100     if (!dladdr(function, &functionInfo) ||
101         !dladdr((void *)malloc, &mallocInfo)) {
102         return false;
103     }
104 
105     return (skipMallocCheck || mallocInfo.dli_fbase == functionInfo.dli_fbase);
106 #else
107     return false;
108 #endif
109 }
110 
111 static inline bool
_CheckMallocTagImpl(const std::string & impl,const char * libname)112 _CheckMallocTagImpl(const std::string& impl, const char* libname)
113 {
114     return (impl.empty()       ||
115             impl == "auto"     ||
116             impl == "agnostic" ||
117             std::strncmp(impl.c_str(), libname, strlen(libname)) == 0);
118 }
119 
120 bool
ArchIsPxmallocActive()121 ArchIsPxmallocActive()
122 {
123     const std::string impl = ArchGetEnv("TF_MALLOC_TAG_IMPL");
124     if (!_CheckMallocTagImpl(impl, "pxmalloc")) {
125         return false;
126     }
127     bool skipMallocCheck = (impl == "pxmalloc force");
128     return _MallocProvidedBySameLibraryAs("__pxmalloc_malloc", skipMallocCheck);
129 }
130 
131 bool
ArchIsPtmallocActive()132 ArchIsPtmallocActive()
133 {
134     const std::string impl = ArchGetEnv("TF_MALLOC_TAG_IMPL");
135     if (!_CheckMallocTagImpl(impl, "ptmalloc")) {
136         return false;
137     }
138     bool skipMallocCheck = (impl == "ptmalloc force");
139     return _MallocProvidedBySameLibraryAs("__ptmalloc3_malloc", skipMallocCheck);
140 }
141 
142 bool
ArchIsJemallocActive()143 ArchIsJemallocActive()
144 {
145     const std::string impl = ArchGetEnv("TF_MALLOC_TAG_IMPL");
146     if (!_CheckMallocTagImpl(impl, "jemalloc")) {
147         return false;
148     }
149     bool skipMallocCheck = (impl == "jemalloc force");
150     return _MallocProvidedBySameLibraryAs("__jemalloc_malloc", skipMallocCheck);
151 }
152 
153 bool
ArchIsStlAllocatorOff()154 ArchIsStlAllocatorOff()
155 {
156 #if defined(ARCH_COMPILER_GCC) || defined(ARCH_COMPILER_ICC) || \
157     defined(ARCH_COMPILER_CLANG)
158     // I'm assuming that ICC compiles will use the gcc STL library.
159 
160     /*
161      * This is a race, but the STL library itself does it this way.
162      * The assumption is that even if you race, you get the same
163      * value.  There's no assurance that the environment variable has
164      * the same setting as when gcc code looked at it, but even if it
165      * isn't, it's just a preference, not behavior that has to correct
166      * to avoid a crash.
167      */
168     static bool isOff = ArchHasEnv("GLIBCXX_FORCE_NEW");
169     return isOff;
170 #else
171     return false;
172 #endif
173 }
174 
175 bool
IsInitialized()176 ArchMallocHook::IsInitialized()
177 {
178     return _underlyingMallocFunc || _underlyingReallocFunc ||
179        _underlyingMemalignFunc || _underlyingFreeFunc;
180 }
181 
182 #if defined(ARCH_OS_LINUX) || defined(ARCH_OS_FREEBSD)
183 template <typename T>
_GetSymbol(T * addr,const char * name,string * errMsg)184 static bool _GetSymbol(T* addr, const char* name, string* errMsg) {
185     if (void* symbol = dlsym(RTLD_DEFAULT, name)) {
186         *addr = (T) symbol;
187         return true;
188     }
189     else {
190         *errMsg = "lookup for symbol '" + string(name) + "' failed";
191         return false;
192     }
193 }
194 
195 static bool
_MallocHookAvailable()196 _MallocHookAvailable()
197 {
198     return (ArchIsPxmallocActive() ||
199             ArchIsPtmallocActive() ||
200             ArchIsJemallocActive());
201 }
202 
203 struct Arch_MallocFunctionNames
204 {
205     const char* mallocFn;
206     const char* reallocFn;
207     const char* memalignFn;
208     const char* freeFn;
209 };
210 
211 static Arch_MallocFunctionNames
_GetUnderlyingMallocFunctionNames()212 _GetUnderlyingMallocFunctionNames()
213 {
214     Arch_MallocFunctionNames names;
215     if (ArchIsPxmallocActive()) {
216         names.mallocFn = "__pxmalloc_malloc";
217         names.reallocFn = "__pxmalloc_realloc";
218         names.memalignFn = "__pxmalloc_memalign";
219         names.freeFn = "__pxmalloc_free";
220     }
221     else if (ArchIsPtmallocActive()) {
222         names.mallocFn = "__ptmalloc3_malloc";
223         names.reallocFn = "__ptmalloc3_realloc";
224         names.memalignFn = "__ptmalloc3_memalign";
225         names.freeFn = "__ptmalloc3_free";
226     }
227     else if (ArchIsJemallocActive()) {
228         names.mallocFn = "__jemalloc_malloc";
229         names.reallocFn = "__jemalloc_realloc";
230         names.memalignFn = "__jemalloc_memalign";
231         names.freeFn = "__jemalloc_free";
232     }
233 
234     return names;
235 }
236 #endif
237 
238 bool
Initialize(ARCH_UNUSED_ARG void * (* mallocWrapper)(size_t,const void *),ARCH_UNUSED_ARG void * (* reallocWrapper)(void *,size_t,const void *),ARCH_UNUSED_ARG void * (* memalignWrapper)(size_t,size_t,const void *),ARCH_UNUSED_ARG void (* freeWrapper)(void *,const void *),string * errMsg)239 ArchMallocHook::Initialize(
240     ARCH_UNUSED_ARG void* (*mallocWrapper)(size_t, const void*),
241     ARCH_UNUSED_ARG void* (*reallocWrapper)(void*, size_t, const void*),
242     ARCH_UNUSED_ARG void* (*memalignWrapper)(size_t, size_t, const void*),
243     ARCH_UNUSED_ARG void  (*freeWrapper)(void*, const void*),
244     string* errMsg)
245 {
246 #if !defined(ARCH_OS_LINUX)
247     *errMsg = "ArchMallocHook functionality not implemented for non-linux systems";
248     return false;
249 #else
250     if (IsInitialized()) {
251         *errMsg = "ArchMallocHook already initialized";
252         return false;
253     }
254 
255     if (!_MallocHookAvailable()) {
256         *errMsg =
257             "ArchMallocHook functionality not available for current allocator";
258         return false;
259     }
260 
261     /*
262      * Ensure initialization of the malloc system hook mechanism.  The sequence
263      * below works for both built-in malloc (i.e. in glibc) and external
264      * ptmalloc3.
265      */
266     free(realloc(malloc(1), 2));
267     free(memalign(sizeof(void*), sizeof(void*)));
268 
269     // We check here that either the hooks are unset, or they're set to malloc,
270     // free, etc.  We do this because at least one allocator (jemalloc)
271     // explicitly sets the hooks to point to its malloc functions to work around
272     // bugs related to shared libraries opened with the DEEPBIND flag picking up
273     // the system (glibc) malloc symbols instead of the custom allocator's
274     // (jemalloc's).  Pixar's pxmalloc wrapper does the same, for the same
275     // reason.
276     if ((__malloc_hook &&
277          __malloc_hook != reinterpret_cast<void *>(malloc)) ||
278         (__realloc_hook &&
279          __realloc_hook != reinterpret_cast<void *>(realloc)) ||
280         (__memalign_hook &&
281          __memalign_hook != reinterpret_cast<void *>(memalign)) ||
282         (__free_hook &&
283          __free_hook != reinterpret_cast<void *>(free))) {
284         *errMsg =
285             "One or more malloc/realloc/free hook variables are already set.\n"
286             "This probably means another entity in the program is trying to\n"
287             "do its own profiling, pre-empting yours.";
288         return false;
289     }
290 
291     /*
292      * We modify _underlyingMallocFunc etc. by reference, to avoid
293      * a complaint about "type-punning" w.r.t. strict-aliasing.
294      */
295     const Arch_MallocFunctionNames names = _GetUnderlyingMallocFunctionNames();
296 
297     if (!_GetSymbol(&_underlyingMallocFunc, names.mallocFn, errMsg) ||
298         !_GetSymbol(&_underlyingReallocFunc, names.reallocFn, errMsg) ||
299         !_GetSymbol(&_underlyingMemalignFunc, names.memalignFn, errMsg) ||
300         !_GetSymbol(&_underlyingFreeFunc, names.freeFn, errMsg)) {
301         return false;
302     }
303 
304     if (mallocWrapper)
305         __malloc_hook = mallocWrapper;
306 
307     if (reallocWrapper)
308         __realloc_hook = reallocWrapper;
309 
310     if (memalignWrapper)
311         __memalign_hook = memalignWrapper;
312 
313     if (freeWrapper)
314         __free_hook = freeWrapper;
315 
316     return true;
317 #endif
318 }
319 
320 PXR_NAMESPACE_CLOSE_SCOPE
321