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