/* ====================================================================
* The Kannel Software License, Version 1.0
*
* Copyright (c) 2001-2005 Kannel Group
* Copyright (c) 1998-2001 WapIT Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Kannel Group (http://www.kannel.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Kannel" and "Kannel Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please
* contact org@kannel.org.
*
* 5. Products derived from this software may not be called "Kannel",
* nor may "Kannel" appear in their name, without prior written
* permission of the Kannel Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 KANNEL GROUP OR ITS 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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Kannel Group. For more information on
* the Kannel Group, please see .
*
* Portions of this software are based upon software originally written at
* WapIT Ltd., Helsinki, Finland for the Kannel project.
*/
/*
* gwmem-check.c - memory management wrapper functions, check flavor
*
* This implementation of the gwmem.h interface checks for writes to
* non-allocated areas, and fills freshly allocated and freshly freed
* areas with garbage to prevent their use. It also reports memory
* leaks.
*
* Design: Memory is allocated with markers before and after the
* area to be used. These markers can be checked to see if anything
* has written to them. There is a table of all allocated areas,
* which is used to detect memory leaks and which contains context
* information about each area.
*
* The start marker contains the index into this table, so that it
* can be looked up quickly -- but if the start marker has been damaged,
* the index can still be found by searching the table.
*
* Enlarging an area with realloc is handled by allocating new area,
* copying the old data, and freeing the old area. This is an expensive
* operation which is avoided by reserving extra space (up to the nearest
* power of two), and only enlarging the area if the requested space is
* larger than this extra space. The markers are still placed at exactly
* the size requested, so every realloc does mean moving the end marker.
*
* When data is freed, it is overwritten with 0xdeadbeef, so that code
* that tries to use it after freeing will likely crash. The freed area
* is kept around for a while, to see if anything tries to write to it
* after it's been freed.
*
* Richard Braakman
* Alexander Malysh (added backtrace support)
*/
#include "gw-config.h"
#include
#include
#include
#if HAVE_BACKTRACE
#include
#endif
#include "gwlib.h"
/* In this module, we must use the real versions so let's undefine the
* accident protectors. */
#undef malloc
#undef realloc
#undef free
/* Freshly malloced space is filled with NEW_AREA_PATTERN, to break
* code that assumes it is filled with zeroes. */
#define NEW_AREA_PATTERN 0xcafebabe
/* Freed space is filled with FREE_AREA_PATTERN, to break code that
* tries to read from it after freeing. */
#define FREE_AREA_PATTERN 0xdeadbeef
/* The marker before an area is filled with START_MARK_PATTERN
* (except for some bookkeeping bytes at the start of the marker). */
#define START_MARK_PATTERN 0xdadaface
/* The marker beyond an area is filled with END_MARK_PATTERN. */
#define END_MARK_PATTERN 0xadadafec
/* How many bytes to dump when listing unfreed areas. */
#define MAX_DUMP 16
static int initialized = 0;
/* Use slower, more reliable method of detecting memory corruption. */
static int slow = 0;
/* We have to use a static mutex here, because otherwise the mutex_create
* call would try to allocate memory with gw_malloc before we're
* initialized. */
static Mutex gwmem_lock;
struct location
{
const char *filename;
long lineno;
const char *function;
};
/* Duplicating the often-identical location information in every table
* entry uses a lot of memory, but saves the effort of maintaining a
* data structure for it, and keeps access to it fast. */
struct area
{
void *area; /* The allocated memory area, as seen by caller */
size_t area_size; /* Size requested by caller */
size_t max_size; /* Size we can expand area to when reallocing */
struct location allocator; /* Caller that alloced area */
struct location reallocator; /* Caller that last realloced area */
struct location claimer; /* Owner of area, set by caller */
#if HAVE_BACKTRACE
void *frames[10]; /* 10 callers should be sufficient */
size_t frame_size;
#endif
};
/* Number of bytes to reserve on either side of each allocated area,
* to detect writes just outside the area. It must be at least large
* enough to hold a long. */
#define MARKER_SIZE 16
/* 100 MB */
#define MAX_TAB_SIZE (100*1024*1024L)
#define MAX_ALLOCATIONS ((long) (MAX_TAB_SIZE/sizeof(struct area)))
/* Freed areas are thrown into the free ring. They are not released
* back to the system until FREE_RING_SIZE other allocations have been
* made. This is more effective at finding bugs than releasing them
* immediately, because when we eventually release them we can check
* that they have not been tampered with in that time. */
#define FREE_RING_SIZE 1024
static struct area allocated[MAX_ALLOCATIONS];
static struct area free_ring[FREE_RING_SIZE];
/* Current number of allocations in the "allocated" table. They are
* always consecutive and start at the beginning of the table. */
static long num_allocations;
/* The free ring can wrap around the edges of its array. */
static long free_ring_start;
static long free_ring_len;
/* The next three are used for informational messages at shutdown */
/* Largest number of allocations we've had at one time */
static long highest_num_allocations;
/* Largest value of the sum of allocated areas we've had at one time */
static long highest_total_size;
/* Current sum of allocated areas */
static long total_size;
/* Static functions */
static inline void lock(void)
{
mutex_lock(&gwmem_lock);
}
static inline void unlock(void)
{
mutex_unlock(&gwmem_lock);
}
static unsigned long round_pow2(unsigned long num)
{
unsigned long i;
if (num <= 16)
return 16;
for (i = 32; i < 0x80000000L; i <<= 1) {
if (num <= i)
return i;
}
/* We have to handle this case separately; the loop cannot go that
* far because i would overflow. */
if (num <= 0x80000000L)
return 0x80000000L;
return 0xffffffffL;
}
/* Fill a memory area with a bit pattern */
static void fill(unsigned char *p, size_t bytes, long pattern)
{
while (bytes > sizeof(pattern)) {
memcpy(p, &pattern, sizeof(pattern));
p += sizeof(pattern);
bytes -= sizeof(pattern);
}
if (bytes > 0)
memcpy(p, &pattern, bytes);
}
/* Check that a filled memory area has not changed */
static int untouched(unsigned char *p, size_t bytes, long pattern)
{
while (bytes > sizeof(pattern)) {
if (memcmp(p, &pattern, sizeof(pattern)) != 0)
return 0;
p += sizeof(pattern);
bytes -= sizeof(pattern);
}
if (bytes > 0 && memcmp(p, &pattern, bytes) != 0)
return 0;
return 1;
}
/* Fill the end marker for this area */
static inline void endmark(unsigned char *p, size_t size)
{
fill(p + size, MARKER_SIZE, END_MARK_PATTERN);
}
/* Fill the start marker for this area, and assign an number to the
* area which can be used for quick lookups later. The number must
* not be negative. */
static void startmark(unsigned char *p, long number)
{
gw_assert(MARKER_SIZE >= sizeof(long));
gw_assert(number >= 0);
fill(p - MARKER_SIZE, sizeof(long), number);
fill(p - MARKER_SIZE + sizeof(long),
MARKER_SIZE - sizeof(long), START_MARK_PATTERN);
}
/* Check that the start marker for this area are intact, and return the
* marker number if it seems intact. Return a negative number if
* it does not seem intact. */
static long check_startmark(unsigned char *p)
{
long number;
if (!untouched(p - MARKER_SIZE + sizeof(long),
MARKER_SIZE - sizeof(long), START_MARK_PATTERN))
return -1;
memcpy(&number, p - MARKER_SIZE, sizeof(number));
return number;
}
static int check_endmark(unsigned char *p, size_t size)
{
if (!untouched(p + size, MARKER_SIZE, END_MARK_PATTERN))
return -1;
return 0;
}
static int check_marks(struct area *area, long index)
{
int result = 0;
if (check_startmark(area->area) != index) {
error(0, "Start marker was damaged for area %ld", index);
result = -1;
}
if (check_endmark(area->area, area->area_size) < 0) {
error(0, "End marker was damaged for area %ld", index);
result = -1;
}
return result;
}
static void dump_area(struct area *area)
{
debug("gwlib.gwmem", 0, "Area %p, size %ld, max_size %ld",
area->area, (long) area->area_size, (long) area->max_size);
debug("gwlib.gwmem", 0, "Allocated by %s() at %s:%ld",
area->allocator.function,
area->allocator.filename,
area->allocator.lineno);
if (area->reallocator.function) {
debug("gwlib.gwmem", 0, "Re-allocated by %s() at %s:%ld",
area->reallocator.function,
area->reallocator.filename,
area->reallocator.lineno);
}
if (area->claimer.function) {
debug("gwlib.gwmem", 0, "Claimed by %s() at %s:%ld",
area->claimer.function,
area->claimer.filename,
area->claimer.lineno);
}
if (area->area_size > 0) {
size_t i;
unsigned char *p;
char buf[MAX_DUMP * 3 + 1];
p = area->area;
buf[0] = '\0';
for (i = 0; i < area->area_size && i < MAX_DUMP; ++i)
sprintf(strchr(buf, '\0'), "%02x ", p[i]);
debug("gwlib.gwmem", 0, "Contents of area (first %d bytes):", MAX_DUMP);
debug("gwlib.gwmem", 0, " %s", buf);
}
#if HAVE_BACKTRACE
{
size_t i;
char **strings = backtrace_symbols(area->frames, area->frame_size);
debug("gwlib.gwmem", 0, "Backtrace of last malloc/realloc:");
for (i = 0; i < area->frame_size; i++) {
if (strings != NULL)
debug("gwlib.gwmem", 0, "%s", strings[i]);
else
debug("gwlib.gwmem", 0, "%p", area->frames[i]);
}
free(strings);
}
#endif
}
static struct area *find_area(unsigned char *p)
{
long index;
struct area *area;
long suspicious_pointer;
unsigned long p_ul;
gw_assert(p != NULL);
p_ul = (unsigned long) p;
suspicious_pointer =
(sizeof(p) == sizeof(long) &&
(p_ul == NEW_AREA_PATTERN || p_ul == FREE_AREA_PATTERN ||
p_ul == START_MARK_PATTERN || p_ul == END_MARK_PATTERN));
if (slow || suspicious_pointer) {
/* Extra check, which does not touch the (perhaps not allocated)
* memory area. It's slow, but may help pinpoint problems that
* would otherwise cause segfaults. */
for (index = 0; index < num_allocations; index++) {
if (allocated[index].area == p)
break;
}
if (index == num_allocations) {
error(0, "Area %p not found in allocation table.", p);
return NULL;
}
}
index = check_startmark(p);
if (index >= 0 && index < num_allocations &&
allocated[index].area == p) {
area = &allocated[index];
if (check_endmark(p, area->area_size) < 0) {
error(0, "End marker was damaged for area %p", p);
dump_area(area);
}
return area;
}
error(0, "Start marker was damaged for area %p", p);
for (index = 0; index < num_allocations; index++) {
if (allocated[index].area == p) {
area = &allocated[index];
dump_area(area);
return area;
}
}
error(0, "Could not find area information.");
return NULL;
}
static void change_total_size(long change)
{
total_size += change;
if (total_size > highest_total_size)
highest_total_size = total_size;
}
static struct area *record_allocation(unsigned char *p, size_t size,
const char *filename, long lineno, const char *function)
{
struct area *area;
static struct area empty_area;
if (num_allocations == MAX_ALLOCATIONS) {
panic(0, "Too many concurrent allocations.");
}
area = &allocated[num_allocations];
*area = empty_area;
area->area = p;
area->area_size = size;
area->max_size = size;
area->allocator.filename = filename;
area->allocator.lineno = lineno;
area->allocator.function = function;
#if HAVE_BACKTRACE
area->frame_size = backtrace(area->frames, sizeof(area->frames) / sizeof(void*));
#endif
startmark(area->area, num_allocations);
endmark(area->area, area->area_size);
num_allocations++;
if (num_allocations > highest_num_allocations)
highest_num_allocations = num_allocations;
change_total_size(size);
return area;
}
static void remove_allocation(struct area *area)
{
change_total_size(-1*area->area_size);
num_allocations--;
if (area == &allocated[num_allocations])
return;
check_marks(&allocated[num_allocations], num_allocations);
*area = allocated[num_allocations];
startmark(area->area, area - allocated);
}
static void drop_from_free_ring(long index)
{
struct area *area;
area = &free_ring[index];
if (check_marks(area, index) < 0 ||
!untouched(area->area, area->area_size, FREE_AREA_PATTERN)) {
error(0, "Freed area %p has been tampered with.", area->area);
dump_area(area);
}
free((unsigned char *)area->area - MARKER_SIZE);
}
static void put_on_free_ring(struct area *area)
{
/* Simple case: We're still filling the free ring. */
if (free_ring_len < FREE_RING_SIZE) {
free_ring[free_ring_len] = *area;
startmark(area->area, free_ring_len);
free_ring_len++;
return;
}
/* Normal case: We need to check and release a free ring entry,
* then put this one in its place. */
drop_from_free_ring(free_ring_start);
free_ring[free_ring_start] = *area;
startmark(area->area, free_ring_start);
free_ring_start = (free_ring_start + 1) % FREE_RING_SIZE;
}
static void free_area(struct area *area)
{
fill(area->area, area->area_size, FREE_AREA_PATTERN);
put_on_free_ring(area);
remove_allocation(area);
}
void gw_check_init_mem(int slow_flag)
{
mutex_init_static(&gwmem_lock);
slow = slow_flag;
initialized = 1;
}
void gw_check_shutdown(void)
{
mutex_destroy(&gwmem_lock);
initialized = 0;
}
void *gw_check_malloc(size_t size, const char *filename, long lineno,
const char *function)
{
unsigned char *p;
gw_assert(initialized);
/* ANSI C89 says malloc(0) is implementation-defined. Avoid it. */
gw_assert(size > 0);
p = malloc(size + 2 * MARKER_SIZE);
if (p == NULL)
panic(errno, "Memory allocation of %ld bytes failed.", (long)size);
p += MARKER_SIZE;
lock();
fill(p, size, NEW_AREA_PATTERN);
record_allocation(p, size, filename, lineno, function);
unlock();
return p;
}
void *gw_check_realloc(void *p, size_t size, const char *filename,
long lineno, const char *function)
{
struct area *area;
if (p == NULL)
return gw_check_malloc(size, filename, lineno, function);
gw_assert(initialized);
gw_assert(size > 0);
lock();
area = find_area(p);
if (!area) {
unlock();
panic(0, "Realloc called on non-allocated area");
}
if (size == area->area_size) {
/* No changes */
} else if (size <= area->max_size) {
change_total_size(size - area->area_size);
area->area_size = size;
endmark(p, size);
} else if (size > area->max_size) {
/* The current block is not large enough for the reallocation.
* We will allocate a new block, copy the data over, and free
* the old block. We round the size up to a power of two,
* to prevent frequent reallocations. */
struct area *new_area;
size_t new_size;
unsigned char *new_p;
new_size = round_pow2(size + 2 * MARKER_SIZE);
new_p = malloc(new_size);
new_size -= 2 * MARKER_SIZE;
new_p += MARKER_SIZE;
memcpy(new_p, p, area->area_size);
fill(new_p + area->area_size, size - area->area_size,
NEW_AREA_PATTERN);
new_area = record_allocation(new_p, size,
area->allocator.filename,
area->allocator.lineno,
area->allocator.function);
new_area->max_size = new_size;
free_area(area);
p = new_p;
area = new_area;
}
area->reallocator.filename = filename;
area->reallocator.lineno = lineno;
area->reallocator.function = function;
unlock();
return p;
}
void gw_check_free(void *p, const char *filename, long lineno,
const char *function)
{
struct area *area;
gw_assert(initialized);
if (p == NULL)
return;
lock();
area = find_area(p);
if (!area) {
unlock();
panic(0, "Free called on non-allocated area");
}
free_area(area);
unlock();
}
char *gw_check_strdup(const char *str, const char *filename, long lineno,
const char *function)
{
char *copy;
gw_assert(initialized);
gw_assert(str != NULL);
copy = gw_check_malloc(strlen(str) + 1, filename, lineno, function);
strcpy(copy, str);
return copy;
}
void *gw_check_claim_area(void *p, const char *filename, long lineno,
const char *function)
{
struct area *area;
/* Allow this for the convenience of wrapper macros. */
if (p == NULL)
return NULL;
lock();
area = find_area(p);
if (!area) {
unlock();
panic(0, "Claim_area called on non-allocated area");
}
area->claimer.filename = filename;
area->claimer.lineno = lineno;
area->claimer.function = function;
unlock();
/* For convenience of calling macros */
return p;
}
void gw_check_check_leaks(void)
{
long calculated_size;
long index;
gw_assert(initialized);
lock();
for (index = 0; index < free_ring_len; index++) {
drop_from_free_ring(index);
}
free_ring_len = 0;
calculated_size = 0;
for (index = 0; index < num_allocations; index++) {
calculated_size += allocated[index].area_size;
}
gw_assert(calculated_size == total_size);
debug("gwlib.gwmem", 0, "----------------------------------------");
debug("gwlib.gwmem", 0, "Current allocations: %ld areas, %ld bytes",
num_allocations, total_size);
debug("gwlib.gwmem", 0, "Highest number of allocations: %ld areas",
highest_num_allocations);
debug("gwlib.gwmem", 0, "Highest memory usage: %ld bytes",
highest_total_size);
for (index = 0; index < num_allocations; index++) {
check_marks(&allocated[index], index);
dump_area(&allocated[index]);
}
unlock();
}
int gw_check_is_allocated(void *p)
{
struct area *area;
lock();
area = find_area(p);
unlock();
return area != NULL;
}
long gw_check_area_size(void *p)
{
struct area *area;
size_t size;
lock();
area = find_area(p);
if (!area) {
unlock();
warning(0, "Area_size called on non-allocated area %p", p);
return -1;
}
size = area->area_size;
unlock();
return size;
}