/*
* ipmi_malloc.c
*
* MontaVista IPMI memory handling code.
*
* Author: MontaVista Software, Inc.
* Corey Minyard <minyard@mvista.com>
* source@mvista.com
*
* Copyright 2002,2003,2004,2005 MontaVista Software Inc.
*
* This software is available to you under a choice of one of two
* licenses. You may choose to be licensed under the terms of the GNU
* Lesser General Public License (GPL) Version 2 or the modified BSD
* license below. The following disclamer applies to both licenses:
*
* THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
*
* GNU Lesser General Public Licence
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Modified BSD Licence
*
* 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 name of the author may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*/
#include <config.h>
#include <string.h>
#ifdef HAVE_EXECINFO_H
#include <execinfo.h> /* For backtrace() */
#endif
#include <OpenIPMI/ipmi_log.h>
#include <OpenIPMI/os_handler.h>
#include <OpenIPMI/internal/ipmi_malloc.h>
#include <OpenIPMI/internal/ilist.h>
void (*ipmi_malloc_log)(enum ipmi_log_type_e log_type, const char *format, ...)
#if __GNUC__ > 2
__attribute__ ((__format__ (__printf__, 2, 3)))
#endif
= NULL;
#define DBG_ALIGN 16
#define TB_SIZE 6
#define SIGNATURE 0x82c2e45a
#define FREE_SIGNATURE 0xb981cef1
#define BYTE_SIGNATURE 0x74
int i__ipmi_debug_malloc = 0;
static os_handler_t *malloc_os_hnd = NULL;
struct dbg_malloc_header
{
unsigned long signature;
unsigned long size;
/*
* The following tb is included even if !HAVE_EXECINFO_H, because
* it is used to detect buffer underruns.
*/
void *tb[TB_SIZE];
};
struct dbg_malloc_trailer
{
/*
* The following tb is included even if !HAVE_EXECINFO_H, because
* it is used to detect buffer overruns.
*/
void *tb[TB_SIZE];
struct dbg_malloc_header *next, *prev;
};
static struct dbg_malloc_header *free_queue = NULL;
static struct dbg_malloc_header *free_queue_tail = NULL;
static int free_queue_len;
static int max_free_queue = 100;
static struct dbg_malloc_header *alloced = NULL;
static struct dbg_malloc_header *alloced_tail = NULL;
static size_t
dbg_align(size_t size)
{
if (size & (DBG_ALIGN-1))
size = (size & (~(DBG_ALIGN - 1))) + DBG_ALIGN;
return size;
}
static struct dbg_malloc_trailer *
trlr_from_hdr(struct dbg_malloc_header *hdr)
{
size_t real_size = dbg_align(hdr->size);
return (struct dbg_malloc_trailer *)
(((char *) hdr) + sizeof(*hdr) + real_size);
}
static void *
ipmi_debug_malloc(size_t size, void **tb)
{
char *data;
struct dbg_malloc_header *hdr;
struct dbg_malloc_trailer *trlr;
struct dbg_malloc_trailer *trlr2;
size_t i;
size_t real_size;
real_size = dbg_align(size);
data = malloc_os_hnd->mem_alloc(real_size
+ sizeof(struct dbg_malloc_header)
+ sizeof(struct dbg_malloc_trailer));
if (!data)
return NULL;
hdr = (struct dbg_malloc_header *) data;
trlr = (struct dbg_malloc_trailer *) (data + real_size + sizeof(*hdr));
hdr->signature = SIGNATURE;
hdr->size = size;
if (tb)
memcpy(hdr->tb, tb, sizeof(hdr->tb));
for (i=0; i<TB_SIZE; i++)
trlr->tb[i] = (void *) SIGNATURE;
data += sizeof(*hdr);
for (i=size; i<real_size; i++)
data[i] = BYTE_SIGNATURE;
/* Add it to the alloced list. */
trlr->next = NULL;
trlr->prev = alloced_tail;
if (alloced_tail) {
trlr2 = trlr_from_hdr(alloced_tail);
trlr2->next = hdr;
} else
alloced = hdr;
alloced_tail = hdr;
return data;
}
static void
mem_debug_log(void *data,
struct dbg_malloc_header *hdr,
struct dbg_malloc_trailer *trlr,
void **tb,
char *text)
{
#ifdef HAVE_EXECINFO_H
int i;
#endif
if (!ipmi_malloc_log)
return;
ipmi_malloc_log(IPMI_LOG_DEBUG_START, "%s", text);
if (hdr) {
ipmi_malloc_log(IPMI_LOG_DEBUG_CONT,
" %ld bytes at %p",
hdr->size, data);
#ifdef HAVE_EXECINFO_H
ipmi_malloc_log(IPMI_LOG_DEBUG_CONT, ", allocated at");
for (i=0; i<TB_SIZE && hdr->tb[i]; i++)
ipmi_malloc_log(IPMI_LOG_DEBUG_CONT, " %p", hdr->tb[i]);
#endif
} else if (data) {
ipmi_malloc_log(IPMI_LOG_DEBUG_CONT, " at %p", data);
}
#ifdef HAVE_EXECINFO_H
if (trlr) {
ipmi_malloc_log(IPMI_LOG_DEBUG_CONT, "\n originally freed at");
for (i=0; i<TB_SIZE && trlr->tb[i]; i++)
ipmi_malloc_log(IPMI_LOG_DEBUG_CONT, " %p", trlr->tb[i]);
}
if (tb) {
ipmi_malloc_log(IPMI_LOG_DEBUG_CONT, "\n at");
for (i=0; i<TB_SIZE && tb[i]; i++)
ipmi_malloc_log(IPMI_LOG_DEBUG_CONT, " %p", tb[i]);
}
#endif
ipmi_malloc_log(IPMI_LOG_DEBUG_END, " ");
}
static void
dbg_remove_free_queue(void)
{
struct dbg_malloc_header *hdr;
struct dbg_malloc_trailer *trlr;
size_t real_size;
unsigned long *dp;
size_t i;
char *data;
int overwrite;
hdr = free_queue;
trlr = trlr_from_hdr(hdr);
free_queue = trlr->next;
if (!free_queue)
free_queue_tail = NULL;
free_queue_len--;
data = ((char *) hdr) + sizeof(*hdr);
if (hdr->signature != FREE_SIGNATURE) {
mem_debug_log(data, hdr, trlr, NULL, "Header overrun");
goto out;
}
real_size = dbg_align(hdr->size);
overwrite = 0;
dp = (unsigned long *) data;
for (i=0; i<real_size; i+=sizeof(unsigned long), dp++)
if (*dp != FREE_SIGNATURE)
overwrite = 1;
if (overwrite)
mem_debug_log(data, hdr, trlr, NULL, "Write while free");
out:
malloc_os_hnd->mem_free(hdr);
}
static void
enqueue_dbg_free(struct dbg_malloc_header *hdr,
struct dbg_malloc_trailer *trlr)
{
while (free_queue_len >= max_free_queue) {
dbg_remove_free_queue();
}
trlr->next = NULL;
if (free_queue_tail) {
struct dbg_malloc_trailer *trlr2;
trlr2 = trlr_from_hdr(free_queue_tail);
trlr2->next = hdr;
} else {
free_queue = hdr;
}
free_queue_tail = hdr;
free_queue_len++;
}
static void
ipmi_debug_free(void *to_free, void **tb)
{
struct dbg_malloc_header *hdr;
struct dbg_malloc_trailer *trlr;
struct dbg_malloc_trailer *trlr2;
size_t i;
size_t real_size;
unsigned long *dp;
char *data = to_free;
int overwrite;
if (to_free == NULL) {
mem_debug_log(data, NULL, NULL, tb, "Free called with NULL");
return;
}
hdr = (struct dbg_malloc_header *) (data - sizeof(*hdr));
if ((hdr->signature != SIGNATURE) && (hdr->signature != FREE_SIGNATURE)) {
mem_debug_log(data, NULL, NULL, tb, "Free of invalid data");
return;
}
trlr = trlr_from_hdr(hdr);
if (hdr->signature == FREE_SIGNATURE) {
mem_debug_log(data, hdr, trlr, tb, "Double free");
return;
}
/* Remove it from the alloced list. */
if (trlr->next) {
trlr2 = trlr_from_hdr(trlr->next);
trlr2->prev = trlr->prev;
} else {
alloced_tail = trlr->prev;
if (alloced_tail) {
trlr2 = trlr_from_hdr(alloced_tail);
trlr2->next = NULL;
}
}
if (trlr->prev) {
trlr2 = trlr_from_hdr(trlr->prev);
trlr2->next = trlr->next;
} else {
alloced = trlr->next;
if (alloced) {
trlr2 = trlr_from_hdr(alloced);
trlr2->prev = NULL;
}
}
real_size = dbg_align(hdr->size);
/* Check for writes after the end of data. */
overwrite = 0;
for (i=0; i<TB_SIZE; i++)
if (trlr->tb[i] != ((void *) SIGNATURE))
overwrite = 1;
for (i=hdr->size; i<real_size; i++)
if (data[i] != BYTE_SIGNATURE)
overwrite = 1;
if (overwrite) {
mem_debug_log(data, hdr, trlr, tb, "Overwrite");
}
hdr->signature = FREE_SIGNATURE;
#ifdef HAVE_EXECINFO_H
memcpy(trlr->tb, tb, sizeof(trlr->tb));
#endif
/* Fill the data area with a signature. */
dp = (unsigned long *) (((char *) hdr) + sizeof(*hdr));
for (i=0; i<real_size; i+=sizeof(unsigned long), dp++)
*dp = FREE_SIGNATURE;
enqueue_dbg_free(hdr, trlr);
}
void
ipmi_debug_malloc_cleanup(void)
{
struct dbg_malloc_trailer *trlr;
if (DEBUG_MALLOC) {
/* Check the free queue for any problems. */
while (free_queue_len > 0) {
dbg_remove_free_queue();
}
/* Now log everything that was still allocated. */
while (alloced) {
trlr = trlr_from_hdr(alloced);
mem_debug_log(((char *) alloced) + sizeof(*alloced),
alloced, NULL, NULL, "Never freed");
alloced = trlr->next;
}
}
}
void *
ipmi_mem_alloc(int size)
{
void *rv;
unsigned char *c;
static int seed;
int i;
if (DEBUG_MALLOC) {
#ifdef HAVE_EXECINFO_H
void *tb[TB_SIZE+1];
memset(tb, 0, sizeof(tb));
backtrace(tb, TB_SIZE+1);
rv = ipmi_debug_malloc(size, tb+1);
#else
rv = ipmi_debug_malloc(size, NULL);
#endif
if (rv) {
c = rv;
/* Fill it with junk to catch using before initializing. */
for (i=0; i<size; i++)
*c++ = i + seed;
seed += size;
}
return rv;
} else
return malloc_os_hnd->mem_alloc(size);
}
void
ipmi_mem_free(void *data)
{
if (DEBUG_MALLOC) {
#ifdef HAVE_EXECINFO_H
void *tb[TB_SIZE+1];
memset(tb, 0, sizeof(tb));
backtrace(tb, TB_SIZE+1);
ipmi_debug_free(data, tb+1);
#else
ipmi_debug_free(data, NULL);
#endif
} else
malloc_os_hnd->mem_free(data);
}
void *
ilist_mem_alloc(size_t size)
{
return ipmi_mem_alloc(size);
}
void
ilist_mem_free(void *data)
{
ipmi_mem_free(data);
}
char *
ipmi_strdup(const char *str)
{
char *rv = ipmi_mem_alloc(strlen(str)+1);
if (!rv)
return NULL;
strcpy(rv, str);
return rv;
}
char *
ipmi_strndup(const char *str, int n)
{
int length;
char *rv = ipmi_mem_alloc(strlen(str)+1);
for (length=0; length<n; length++) {
if (! str[length])
break;
}
rv = ipmi_mem_alloc(length+1);
if (!rv)
return NULL;
memcpy(rv, str, length);
return rv;
}
int
ipmi_malloc_init(os_handler_t *os_hnd)
{
if (!malloc_os_hnd)
malloc_os_hnd = os_hnd;
return 0;
}
void
ipmi_malloc_shutdown(void)
{
malloc_os_hnd = NULL;
}