/* -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*- */
/* vim:set sts=4 ts=8: */
/*
* Copyright (c) 2001-2007 International Computer Science Institute
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software")
* to deal in the Software without restriction, subject to the conditions
* listed in the XORP LICENSE file. These conditions include: you must
* preserve this copyright notice, and you cannot mention the copyright
* holders in advertising related to the Software without their permission.
* The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
* notice is a summary of the XORP LICENSE file; the license in that file is
* legally binding.
*/
#ident "$XORP: xorp/utils/flower_malloc/flower_malloc.c,v 1.7 2007/02/16 22:47:33 pavlin Exp $"
/*
* Library interposer to collect malloc/calloc/realloc statistics
*
* To compile use:
* gcc -shared -fpic -o flower_malloc.so flower_malloc.c
*
* To run use (in BASH):
* LD_PRELOAD=$cwd/flower_malloc.so <your application here>
*
* The results will be in ./flower_report.<prog_name>.<pid> for each
* process invoked by current application.
*
* How to use it...
*
* Start your application as mentioned above, and send it SIGUSR2 whenever you
* want to examine the state of the different allocation points in your
* program.
*
* If you see that one of the tracked allocation points growing suspciously,
* create a file called flower_limits.cfg, with the same format a the report
* file generated by flower_malloc. When a SIGUSR2 signal is sent to
* flower_malloc, it checks for such a file, and if found, it will load the
* limits specified therein for 'allocs' and 'total size' (the other fields are
* ignored). When any tracker exceeds those limits, the program will abort.
* For instance, if your flower_report looks like this:
*
***********************************************
allocs/watermark total size/watermark avg size tracker_id
0/120 0/252000 2100 3bab4
300/300 90000/90000 300 3d55c +
4/5 280000/350000 70000 3d7ac +
3/3 20480/20480 6826 4d3a9
* and you are worried about the allocation tracked by tracker_id 3d55c, you
* can edit the columns 'allocs' and/or 'total size' on the file to be:
***********************************************
allocs/watermark total size/watermark avg size tracker_id
200/300 80000/90000 300 3d55c +
*
* and save it as flower_limits.cfg. You must remove the trackers you are not
* interested on. The next time you run your program, it will abort when there
* are 200 outstanding allocations or 80 kbytes from that point in your
* program. If the limits you provide have already been exceeded by the time
* you send the SIGUSR2 signal, flower_malloc will abort the next time an
* allocation happens from the execution point identified by the tracker.
*
*
* Sending a SIGUSR2 will also force flower_malloc to read your
* limits configuration file, so you will not need to restart your application.
*
* SIGUSR1 can be used to reset all trackers.
*/
#include "libxorp/xorp.h"
#ifdef HOST_OS_FREEBSD
#include <dlfcn.h>
#include <memory.h>
#include <assert.h>
#ifdef APPL_IS_MULTITHREADED
#include <thread.h>
#endif /* APPL_IS_MULTITHREADED */
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <stdlib.h>
#include <limits.h>
#include <unistd.h>
/*
* XXX: If BACKTRACE_DEPTH is larger than 2, then the program coredumps
* on amd64 + FreeBSD-6.1.
* If it is larger than 4, the program coredumps on i386 + FreeBSD-4.10.
*/
#define BACKTRACE_DEPTH 2
#define HASH_TABLE_SIZE 343051 /* prime, max 31 bits */
#define ADMIN_DATA_SIZE 2 * sizeof(uint32_t)
typedef struct alloc_tracker {
uint32_t count;
uint32_t max_count;
uint32_t last_read_max;
uint32_t total_size;
uint32_t watermark;
} alloc_tracker_t;
typedef struct limit_tracker {
uint32_t enabled;
uint32_t count;
uint32_t total_size;
} limit_tracker_t;
static FILE *output = NULL;
static int pid;
static char path[64] = "uninitialized";
static alloc_tracker_t tracker_table[HASH_TABLE_SIZE];
static limit_tracker_t limits_table[HASH_TABLE_SIZE];
static int32_t no_tracking;
/* prototypes */
static uint32_t within_limits (uint32_t trk_idx);
static void init_flower_malloc(void);
/***************************************************************************
* print_data - dumps the state of the alloc tracking table
*
*/
static void
print_data(void)
{
int i;
char growing[2] = " ";
no_tracking = 1; /* prevent mallocs in fprintf from spoiling our report */
fprintf(output, "***********************************************\n");
fprintf(output, "allocs/watermark total size/watermark");
fprintf(output, " avg size tracker_id [+]\n");
for (i=0; i < HASH_TABLE_SIZE; i++)
if (tracker_table[i].watermark) {
fprintf(output, "%8u/%-8u %10u/%-10u ",
tracker_table[i].count, tracker_table[i].max_count,
tracker_table[i].total_size, tracker_table[i].watermark);
if (tracker_table[i].count)
fprintf(output, "%8u ",
tracker_table[i].total_size / tracker_table[i].count);
else /* use watermark to determine avg size */
fprintf(output, "%8u ",
tracker_table[i].watermark / tracker_table[i].max_count);
if (tracker_table[i].max_count > tracker_table[i].last_read_max) {
if (tracker_table[i].last_read_max) growing[0] = '+';
tracker_table[i].last_read_max = tracker_table[i].max_count;
} else growing[0] = ' ';
fprintf(output, "%8x %s\n", i, growing);
}
fflush(output); /* untentionally leaving file open */
no_tracking = 0;
}
/***************************************************************************
* read_config - read flower_malloc config file
*
*/
static void
read_config(void)
{
unsigned long trk_idx, count, max_count, total_size, watermark, avg_size;
int assig;
FILE *config;
#define MAX_CFG_LINE_WIDTH 80
char ignored[MAX_CFG_LINE_WIDTH];
config = fopen("./flower_limits.cfg","r");
if (!config) return;
no_tracking = 1; /* prevent mallocs in fprintf from spoiling our report */
do {
assig = fscanf(config, "%8lu/%8lu %10lu/%10lu %8lu %8lx %*[+ ]\n",
&count, &max_count, &total_size, &watermark, &avg_size, &trk_idx);
if (assig == 6) {
fprintf(output, "Abort if tracker %8lx exceeds %8lu outst "
"allocs or %10lu bytes\n", trk_idx, count, total_size);
fflush(stdout);
limits_table[trk_idx].enabled = 1;
limits_table[trk_idx].count = count;
limits_table[trk_idx].total_size = total_size;
} else {
fgets(ignored, MAX_CFG_LINE_WIDTH, config);
}
} while (assig != EOF);
fclose(config);
no_tracking = 0;
}
/***************************************************************************
* sigusr1_handler - handler for the SIGUSR1 signal
*
* This signal is used to reset the tracker and limits tables
*/
static void
sigusr1_handler(int unused)
{
uint32_t i;
unused = unused;
for (i=0; i < HASH_TABLE_SIZE; i++) {
tracker_table[i].count = 0;
tracker_table[i].max_count = 0;
tracker_table[i].last_read_max = 0;
tracker_table[i].total_size = 0;
tracker_table[i].watermark = 0;
limits_table[i].enabled = 0;
limits_table[i].count = 0;
limits_table[i].total_size = 0;
}
}
/***************************************************************************
* sigusr2_handler - handler for the SIGUSR2 signal
*
* This signal is used to dump the current state of the alloc tracking table to
* the output file.
*/
static void
sigusr2_handler(int unused)
{
unused = unused;
read_config();
print_data();
}
/***************************************************************************
* within_limits - check if a tracker is within its configured limits
*
* Return zero if not
*/
static uint32_t
within_limits (uint32_t trk_idx)
{
if (!limits_table[trk_idx].enabled) return 1;
return (tracker_table[trk_idx].count <= limits_table[trk_idx].count &&
tracker_table[trk_idx].total_size <= limits_table[trk_idx].total_size);
}
/***************************************************************************
* hash - calculate hash value based on backtrace stack
*
* This will be used to assign the same tracker to all the allocations that are
* made from the same point in a program.
*/
static uint32_t
hash (void ** addr_array, size_t depth)
{
size_t i;
size_t hash_value = 0;
for (i=0; i<depth; i++) {
size_t v = (size_t)addr_array[i];
hash_value ^= (v << (i+1));
}
hash_value %= HASH_TABLE_SIZE;
return (uint32_t)hash_value;
}
/***************************************************************************
* get_backtrace - copy the current stack backtrace onto ret_addr array
*
* This function uses gcc built-in function __builtin_return_address to fetch
* the return addresses from the stack. Since this function only accepts
* constant integers as arguments, we must use ugly preprocessor tricks to
* allow for configurable backtrace depths.
*/
static void
get_backtrace(void ** ret_addr)
{
ret_addr[0] = __builtin_return_address(1);
#if BACKTRACE_DEPTH > 1
ret_addr[1] = __builtin_return_address(2);
#if BACKTRACE_DEPTH > 2
ret_addr[2] = __builtin_return_address(3);
#if BACKTRACE_DEPTH > 3
ret_addr[3] = __builtin_return_address(4);
#if BACKTRACE_DEPTH > 4
ret_addr[4] = __builtin_return_address(5);
#if BACKTRACE_DEPTH > 5
ret_addr[5] = __builtin_return_address(6);
#if BACKTRACE_DEPTH > 6
ret_addr[6] = __builtin_return_address(7);
#if BACKTRACE_DEPTH > 7
ret_addr[7] = __builtin_return_address(8);
#if BACKTRACE_DEPTH > 8
ret_addr[8] = __builtin_return_address(9);
#if BACKTRACE_DEPTH > 9
ret_addr[9] = __builtin_return_address(10);
#if BACKTRACE_DEPTH > 10
#error "maximum supported BACKTRACE_DEPTH is 10"
#endif /* 1 */
#endif /* 2 */
#endif /* 3 */
#endif /* 4 */
#endif /* 5 */
#endif /* 6 */
#endif /* 7 */
#endif /* 8 */
#endif /* 9 */
#endif /* 10 */
}
static void
init_flower_malloc()
{
char procbuf[32];
char procname[20];
char prog_name[32];
int fd;
static int initialized = 0;
if (initialized) return;
/*
* Get current executable's name using proc(4) interface
*/
pid = (int)getpid ();
(void)sprintf (procbuf, "/proc/%ld/status", (long)pid);
if ((fd = open (procbuf, O_RDONLY)) != -1) {
if (read (fd, procname, sizeof (procname)) == sizeof (procname)) {
strtok (procname, " ");
sprintf (prog_name, "%s.%d", procname, pid);
} else
sprintf (prog_name, "%s.%d", "unknown", pid);
} else
sprintf (prog_name, "%s.%d", "unknown", pid);
sprintf (path, "%s%s", "./flower_report.",
prog_name);
if (!output) {
output = fopen (path, "a");
if (!output) {
fprintf(stderr, "unable to write to file %s\n", path);
/* invoke original exit(), not the one we implement here */
(*((void (*) (int))dlsym (RTLD_NEXT, "exit"))) (-1);
}
}
fprintf(output, "flower_malloc report for program %s\n\n", prog_name);
fflush(output);
signal(SIGUSR1, sigusr1_handler);
signal(SIGUSR2, sigusr2_handler);
initialized = 1;
return;
}
/***************************************************************************
* track_alloc - track a new allocation
*
* This function updates the allocation tracker for this new allocation, and
* marks the new memory block with the tracker index, and the size of the
* requested memory block. Those two values are stored in the allocated memory
* block, this is why the function requires ptr to point to a fresh memory
* block of size req_size + 8 bytes.
*
* The function returns a pointer to the memory immediately following the marks
* which should be returned to the user.
*
* +---------+---------+---------+---------+---------+----
* | trk_idx | size | (memory block of size req_size) ...
* +---------+---------+---------+---------+---------+----
* ^ ^
* | |
* ptr returned ptr
*/
static void *
track_alloc (uint8_t *ptr, size_t req_size)
{
uint32_t trk_idx;
void * ret_addr[BACKTRACE_DEPTH];
if (no_tracking) return ptr;
get_backtrace(ret_addr);
trk_idx = hash(ret_addr,BACKTRACE_DEPTH);
tracker_table[trk_idx].count++;
tracker_table[trk_idx].total_size += req_size;
if (tracker_table[trk_idx].total_size > tracker_table[trk_idx].watermark)
tracker_table[trk_idx].watermark = tracker_table[trk_idx].total_size;
if (tracker_table[trk_idx].count > tracker_table[trk_idx].max_count)
tracker_table[trk_idx].max_count = tracker_table[trk_idx].count;
if (!within_limits(trk_idx)) {
fprintf(output,"\nTracker %x exceeding limits - ABORTING\n", trk_idx);
print_data();
abort();
}
* (uint32_t*) ptr = trk_idx;
ptr += sizeof(uint32_t);
* (uint32_t*) ptr = req_size;
ptr += sizeof(uint32_t);
return ptr;
}
/***************************************************************************
* track_free - track a free
*
* This function updates the allocation tracker for this free operation.
*
* The function takes the pointer to the memory passed by the user, and returns
* the pointer that is to be freed
*
* +---------+---------+---------+---------+---------+----
* | trk_idx | size | (memory block of size req_size) ...
* +---------+---------+---------+---------+---------+----
* ^ ^
* | |
* returned ptr ptr
*/
static void *
track_free (uint8_t *ptr)
{
uint32_t trk_idx, req_size;
if (no_tracking) return ptr;
ptr -= sizeof(uint32_t);
req_size = * (uint32_t*) ptr;
ptr -= sizeof(uint32_t);
trk_idx = * (uint32_t*) ptr;
if (tracker_table[trk_idx].count) tracker_table[trk_idx].count--;
if (tracker_table[trk_idx].total_size >= req_size)
tracker_table[trk_idx].total_size -= req_size;
return ptr;
}
void exit (int status)
{
/*
* (Re)open the file here since the shell closes all file descriptors
* before calling exit()
*/
output = fopen (path, "a");
if (output)
print_data();
(*((void (*) (int))dlsym (RTLD_NEXT, "exit"))) (status);
abort(); /* should never get here */
}
void *
malloc (size_t size)
{
static void *(*func) (size_t);
void *ret;
if (!func) {
func = (void *(*) (size_t))dlsym (RTLD_NEXT, "malloc");
init_flower_malloc();
}
ret = func (size + ADMIN_DATA_SIZE);
return (track_alloc (ret, size));
}
#if ORIGINAL_CALLOC_DOES_NOT_CALL_MALLOC
void *
calloc (size_t nelem, size_t elsize)
{
static void *(*func) (size_t, size_t);
void *ret;
int i;
if (!func) {
func = (void *(*) (size_t, size_t))dlsym (RTLD_NEXT, "calloc");
init_flower_malloc();
}
ret = func (elsize * nelem + ADMIN_DATA_SIZE, 1);
return (track_alloc(ret, elsize * nelem));
}
#endif
void *
realloc (void *ptr, size_t size)
{
static void *(*func) (void *, size_t);
void *ret;
if (!func) {
func = (void *(*) (void *, size_t))dlsym (RTLD_NEXT, "realloc");
init_flower_malloc();
}
ret = func (track_free(ptr), size + ADMIN_DATA_SIZE);
return (track_alloc(ret, size));
}
void
free (void *ptr)
{
static void (*func) (void *);
if (!func) {
func = (void (*) (void *))dlsym (RTLD_NEXT, "free");
init_flower_malloc();
}
func (track_free(ptr));
return;
}
#endif /* HOST_OS_FREEBSD */
syntax highlighted by Code2HTML, v. 0.9.1