// Copyright (c) 2005, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// ---
// Author: Sanjay Ghemawat <opensource@google.com>
#include "config.h"
// Disable the glibc prototype of mremap(), as older versions of the
// system headers define this function with only four arguments,
// whereas newer versions allow an optional fifth argument:
#ifdef HAVE_MMAP
# define mremap glibc_mremap
# include <sys/mman.h>
# undef mremap
#endif
#include <algorithm>
#include <google/malloc_hook.h>
#include "base/basictypes.h"
#include "base/logging.h"
#include <google/stacktrace.h>
// __THROW is defined in glibc systems. It means, counter-intuitively,
// "This function will never throw an exception." It's an optional
// optimization tool, but we may need to use it to match glibc prototypes.
#ifndef __THROW // I guess we're not on a glibc system
# define __THROW // __THROW is just an optimization, so ok to make it ""
#endif
using std::copy;
// Declarations of three default weak hook functions, that can be overridden by
// linking-in a strong definition (as heap-checker.cc does)
//
// These default hooks let some other library we link in
// to define strong versions of InitialMallocHook_New, InitialMallocHook_MMap,
// and InitialMallocHook_Sbrk to have a chance to hook into the very
// first invocation of an allocation function call, mmap, or sbrk.
//
// These functions are declared here as weak, and defined later, rather than a
// more straightforward simple weak definition, as a workround for an icc
// compiler issue ((Intel reference 290819). This issue causes icc to resolve
// weak symbols too early, at compile rather than link time. By declaring it
// (weak) here, then defining it below after its use, we can avoid the problem.
//
ATTRIBUTE_WEAK
extern void InitialMallocHook_New(const void* ptr, size_t size);
ATTRIBUTE_WEAK
extern void InitialMallocHook_MMap(const void* result,
const void* start,
size_t size,
int protection,
int flags,
int fd,
off_t offset);
ATTRIBUTE_WEAK
extern void InitialMallocHook_Sbrk(const void* result, ptrdiff_t increment);
MallocHook::NewHook MallocHook::new_hook_ = InitialMallocHook_New;
MallocHook::DeleteHook MallocHook::delete_hook_ = NULL;
MallocHook::MmapHook MallocHook::mmap_hook_ = InitialMallocHook_MMap;
MallocHook::MunmapHook MallocHook::munmap_hook_ = NULL;
MallocHook::MremapHook MallocHook::mremap_hook_ = NULL;
MallocHook::SbrkHook MallocHook::sbrk_hook_ = InitialMallocHook_Sbrk;
// The definitions of weak default malloc hooks (New, MMap, and Sbrk)
// that self deinstall on their first call. This is entirely for
// efficiency: the default version of these functions will be called a
// maximum of one time. If these functions were a no-op instead, they'd
// be called every time, costing an extra function call per malloc.
//
// However, this 'delete self' isn't safe in general -- it's possible
// that this function will be called via a daisy chain. That is,
// someone else might do
// old_hook = MallocHook::SetNewHook(&myhook);
// void myhook(void* ptr, size_t size) {
// do_my_stuff();
// old_hook(ptr, size); // daisy-chain the hooks
// }
// If old_hook is InitialMallocHook_New(), then this is broken code! --
// after the first run it'll deregister not only InitialMallocHook_New()
// but also myhook. To protect against that, InitialMallocHook_New()
// makes sure it's the 'top-level' hook before doing the deregistration.
// This means the daisy-chain case will be less efficient because the
// hook will be called, and do an if check, for every new. Alas.
// TODO(csilvers): add support for removing a hook from the middle of a chain.
void InitialMallocHook_New(const void* ptr, size_t size) {
if (MallocHook::GetNewHook() == &InitialMallocHook_New)
MallocHook::SetNewHook(NULL);
}
void InitialMallocHook_MMap(const void* result,
const void* start,
size_t size,
int protection,
int flags,
int fd,
off_t offset) {
if (MallocHook::GetMmapHook() == &InitialMallocHook_MMap)
MallocHook::SetMmapHook(NULL);
}
void InitialMallocHook_Sbrk(const void* result, ptrdiff_t increment) {
if (MallocHook::GetSbrkHook() == &InitialMallocHook_Sbrk)
MallocHook::SetSbrkHook(NULL);
}
DEFINE_ATTRIBUTE_SECTION_VARS(google_malloc);
DECLARE_ATTRIBUTE_SECTION_VARS(google_malloc);
// actual functions are in debugallocation.cc or tcmalloc.cc
DEFINE_ATTRIBUTE_SECTION_VARS(malloc_hook);
DECLARE_ATTRIBUTE_SECTION_VARS(malloc_hook);
// actual functions are in this file, malloc_hook.cc, and low_level_alloc.cc
#define ADDR_IN_ATTRIBUTE_SECTION(addr, name) \
(reinterpret_cast<uintptr_t>(ATTRIBUTE_SECTION_START(name)) <= \
reinterpret_cast<uintptr_t>(addr) && \
reinterpret_cast<uintptr_t>(addr) < \
reinterpret_cast<uintptr_t>(ATTRIBUTE_SECTION_STOP(name)))
// Return true iff 'caller' is a return address within a function
// that calls one of our hooks via MallocHook:Invoke*.
// A helper for GetCallerStackTrace.
static inline bool InHookCaller(const void* caller) {
return ADDR_IN_ATTRIBUTE_SECTION(caller, google_malloc) ||
ADDR_IN_ATTRIBUTE_SECTION(caller, malloc_hook);
// We can use one section for everything except tcmalloc_or_debug
// due to its special linkage mode, which prevents merging of the sections.
}
#undef ADDR_IN_ATTRIBUTE_SECTION
static bool checked_sections = false;
static inline void CheckInHookCaller() {
if (!checked_sections) {
INIT_ATTRIBUTE_SECTION_VARS(google_malloc);
if (ATTRIBUTE_SECTION_START(google_malloc) ==
ATTRIBUTE_SECTION_STOP(google_malloc)) {
RAW_LOG(ERROR, "google_malloc section is missing, "
"thus InHookCaller is broken!");
}
INIT_ATTRIBUTE_SECTION_VARS(malloc_hook);
if (ATTRIBUTE_SECTION_START(malloc_hook) ==
ATTRIBUTE_SECTION_STOP(malloc_hook)) {
RAW_LOG(ERROR, "malloc_hook section is missing, "
"thus InHookCaller is broken!");
}
checked_sections = true;
}
}
// We can improve behavior/compactness of this function
// if we pass a generic test function (with a generic arg)
// into the implementations for GetStackTrace instead of the skip_count.
int MallocHook::GetCallerStackTrace(void** result, int max_depth,
int skip_count) {
#ifndef HAVE_ATTRIBUTE_SECTION_START
// Fall back to GetStackTrace and good old but fragile frame skip counts.
// Note: this path is inaccurate when a hook is not called directly by an
// allocation function but is daisy-chained through another hook,
// search for MallocHook::(Get|Set|Invoke)* to find such cases.
return GetStackTrace(result, max_depth, skip_count + int(DEBUG_MODE));
// due to -foptimize-sibling-calls in opt mode
// there's no need for extra frame skip here then
#endif
CheckInHookCaller();
// MallocHook caller determination via InHookCaller works, use it:
static const int kMaxSkip = 32 + 6 + 3;
// Constant tuned to do just one GetStackTrace call below in practice
// and not get many frames that we don't actually need:
// currently max passsed max_depth is 32,
// max passed/needed skip_count is 6
// and 3 is to account for some hook daisy chaining.
static const int kStackSize = kMaxSkip + 1;
void* stack[kStackSize];
int depth = GetStackTrace(stack, kStackSize, 1); // skip this function frame
if (depth == 0) // silenty propagate cases when GetStackTrace does not work
return 0;
for (int i = 0; i < depth; ++i) { // stack[0] is our immediate caller
if (InHookCaller(stack[i])) {
RAW_VLOG(4, "Found hooked allocator at %d: %p <- %p",
i, stack[i], stack[i+1]);
i += 1; // skip hook caller frame
depth -= i; // correct depth
if (depth > max_depth) depth = max_depth;
copy(stack + i, stack + i + depth, result);
if (depth < max_depth && depth + i == kStackSize) {
// get frames for the missing depth
depth +=
GetStackTrace(result + depth, max_depth - depth, 1 + kStackSize);
}
return depth;
}
}
RAW_LOG(WARNING, "Hooked allocator frame not found, returning empty trace");
// Try increasing kMaxSkip or else something must be wrong with InHookCaller
return 0;
}
// On Linux/x86, we override mmap/munmap/mremap/sbrk
// and provide support for calling the related hooks.
//
// We define mmap() and mmap64(), which somewhat reimplements libc's mmap
// syscall stubs. Unfortunately libc only exports the stubs via weak symbols
// (which we're overriding with our mmap64() and mmap() wrappers) so we can't
// just call through to them.
#if defined(__linux) && (defined(__i386__) || defined(__x86_64__))
#include <unistd.h>
#include <syscall.h>
#include <sys/mman.h>
#include <errno.h>
#include "base/linux_syscall_support.h"
// The x86-32 case and the x86-64 case differ:
// 32b has a mmap2() syscall, 64b does not.
// 64b and 32b have different calling conventions for mmap().
#if defined(__i386__)
static inline void* do_mmap64(void *start, size_t length,
int prot, int flags,
int fd, __off64_t offset) __THROW {
void *result;
// Try mmap2() unless it's not supported
static bool have_mmap2 = true;
if (have_mmap2) {
static int pagesize = 0;
if (!pagesize) pagesize = getpagesize();
// Check that the offset is page aligned
if (offset & (pagesize - 1)) {
result = MAP_FAILED;
errno = EINVAL;
goto out;
}
result = (void *)syscall(SYS_mmap2,
start, length, prot, flags, fd, offset / pagesize);
if (result != MAP_FAILED || errno != ENOSYS) goto out;
// We don't have mmap2() after all - don't bother trying it in future
have_mmap2 = false;
}
if (((off_t)offset) != offset) {
// If we're trying to map a 64-bit offset, fail now since we don't
// have 64-bit mmap() support.
result = MAP_FAILED;
errno = EINVAL;
goto out;
}
{
// Fall back to old 32-bit offset mmap() call
// Old syscall interface cannot handle six args, so pass in an array
int32 args[6] = { (int32) start, length, prot, flags, fd, (off_t) offset };
result = (void *)syscall(SYS_mmap, args);
}
out:
return result;
}
# elif defined(__x86_64__)
static inline void* do_mmap64(void *start, size_t length,
int prot, int flags,
int fd, __off64_t offset) __THROW {
return (void *)syscall(SYS_mmap, start, length, prot, flags, fd, offset);
}
# endif
// We use do_mmap64 abstraction to put MallocHook::InvokeMmapHook
// calls right into mmap and mmap64, so that the stack frames in the caller's
// stack are at the same offsets for all the calls of memory allocating
// functions.
// Put all callers of MallocHook::Invoke* in this module into
// malloc_hook section,
// so that MallocHook::GetCallerStackTrace can function accurately:
extern "C" {
void* mmap64(void *start, size_t length, int prot, int flags,
int fd, __off64_t offset ) __THROW
ATTRIBUTE_SECTION(malloc_hook);
void* mmap(void *start, size_t length,int prot, int flags,
int fd, off_t offset) __THROW
ATTRIBUTE_SECTION(malloc_hook);
int munmap(void* start, size_t length) __THROW
ATTRIBUTE_SECTION(malloc_hook);
void* mremap(void* old_addr, size_t old_size, size_t new_size,
int flags, ...) __THROW
ATTRIBUTE_SECTION(malloc_hook);
void* sbrk(ptrdiff_t increment) __THROW
ATTRIBUTE_SECTION(malloc_hook);
}
extern "C" void* mmap64(void *start, size_t length, int prot, int flags,
int fd, __off64_t offset) __THROW {
void *result = do_mmap64(start, length, prot, flags, fd, offset);
MallocHook::InvokeMmapHook(result, start, length, prot, flags, fd, offset);
return result;
}
extern "C" void* mmap(void *start, size_t length, int prot, int flags,
int fd, off_t offset) __THROW {
void *result = do_mmap64(start, length, prot, flags, fd,
static_cast<size_t>(offset)); // avoid sign extension
MallocHook::InvokeMmapHook(result, start, length, prot, flags, fd, offset);
return result;
}
extern "C" int munmap(void* start, size_t length) __THROW {
MallocHook::InvokeMunmapHook(start, length);
return syscall(SYS_munmap, start, length);
}
extern "C" void* mremap(void* old_addr, size_t old_size, size_t new_size,
int flags, ...) __THROW {
va_list ap;
va_start(ap, flags);
void *new_address = va_arg(ap, void *);
va_end(ap);
void* result = sys_mremap(old_addr, old_size, new_size, flags, new_address);
MallocHook::InvokeMremapHook(result, old_addr, old_size, new_size, flags,
new_address);
return result;
}
// libc's version:
extern "C" void* __sbrk(ptrdiff_t increment);
extern "C" void* sbrk(ptrdiff_t increment) __THROW {
void *result = __sbrk(increment);
MallocHook::InvokeSbrkHook(result, increment);
return result;
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1