#!/bin/ksh


# mpatrol
# A library for controlling and tracing dynamic memory allocations.
# Copyright (C) 1997-2000 Graeme S. Roy <graeme@epc.co.uk>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free
# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.


# UNIX shell script to run programs using the mpatrol library


# $Id: mpatrol,v 1.6 2000/03/07 20:55:24 graeme Exp $


# Display a command usage message.

usage()
{
cat <<ENDUSAGE
$command $version
A command for controlling and tracing dynamic memory allocations.
Copyright (C) 1997-2000 Graeme S. Roy <graeme@epc.co.uk>

Usage:
    $command [options] <command> [arguments]

Options:
    -A <unsigned integer>
        Specifies an allocation index at which to stop the program when it is
        being allocated.
    -a <unsigned integer>
        Specifies an 8-bit byte pattern with which to prefill newly-allocated
        memory.
    -C <unsigned range>
        Specifies a range of allocation indices at which to check the integrity
        of free memory and overflow buffers.
    -c
        Specifies that all arguments to functions which allocate, reallocate
        and deallocate memory have rigorous checks performed on them.
    -D <unsigned integer>
        Specifies the default alignment for general-purpose memory
        allocations, which must be a power of two.
    -d
        Specifies that the LD_PRELOAD environment variable should be set so
        that even programs that were not compiled with the mpatrol library can
        be traced, but only if they were dynamically linked.
    -e <string>
        Specifies an alternative filename with which to locate the executable
        file containing the program's symbols.
    -F <unsigned integer>
        Specifies an allocation index at which to stop the program when it is
        being freed.
    -f <unsigned integer>
        Specifies an 8-bit byte pattern with which to prefill newly-freed
        memory.
    -G
        Instructs the library to save and replace certain signal handlers
        during the execution of library code and to restore them afterwards.
    -g
        Specifies that any debugging information in the executable file
        should be used to obtain additional source-level information.
    -L <unsigned integer>
        Specifies the limit in bytes at which all memory allocations should
        fail if the total allocated memory should increase beyond this.
    -l <string>
        Specifies an alternative file in which to place all diagnostics from
        the mpatrol library.
    -m
        Specifies that the library should use mmap() instead of sbrk() to
        allocate system memory.
    -N
        Specifies that the mpatrol library's internal data structures should
        not be made read-only after every memory allocation, reallocation or
        deallocation.
    -n
        Specifies that the mpatrol library should keep all reallocated and
        freed memory allocations.
    -O <unsigned integer>
        Specifies the size in bytes to use for all overflow buffers, which
        must be a power of two.
    -o <unsigned integer>
        Specifies an 8-bit byte pattern with which to fill the overflow
        buffers of all memory allocations.
    -P
        Specifies that each individual memory allocation should occupy at
        least one page of virtual memory and should be placed at the highest
        point within these pages.
    -p
        Specifies that each individual memory allocation should occupy at
        least one page of virtual memory and should be placed at the lowest
        point within these pages.
    -R <unsigned integer>
        Specifies an allocation index at which to stop the program when a
        memory allocation is being reallocated.
    -S
        Specifies that a memory map of the entire heap and a summary of all of
        the function symbols read from the program's executable file should be
        displayed at the end of program execution.
    -s
        Specifies that a summary of all of the freed and unfreed memory
        allocations should be displayed at the end of program execution.
    -U <unsigned integer>
        Specifies the minimum number of unfreed allocations at which to abort
        the program just before program termination.
    -v
        Specifies that any reallocated or freed memory allocations should
        preserve their original contents.
    -w
        Specifies that watch point areas should be used for overflow buffers
        rather than filling with the overflow byte.
    -Z <unsigned integer>
        Specifies the random number seed which will be used when determining
        which memory allocations will randomly fail.
    -z <unsigned integer>
        Specifies the frequency at which all memory allocations will randomly
        fail.

ENDUSAGE
}


# Set up command and option variables.

command=`basename "$0"`
version="1.5"
options="A:a:C:cD:de:F:f:GgL:l:mNnO:o:PpR:SsU:vwZ:z:"
preload="false"
format="elf"


# The following options correspond to their uppercase equivalents when
# setting the environment variable containing mpatrol library options.

allocbyte=""
allocstop=""
check=""
checkall="false"
defalign=""
failfreq=""
failseed=""
freebyte=""
freestop=""
limit=""
logfile="mpatrol.%n.log"
nofree="false"
noprotect="false"
oflowbyte=""
oflowsize=""
oflowwatch="false"
pagealloc=""
preserve="false"
progfile=""
reallocstop=""
safesignals="false"
showfreed="false" # also includes showunfreed
showmap="false"   # also includes showsymbols
unfreedabort=""
usedebug="false"
usemmap="false"


# Attempt to parse any options given on the command line.
# We use the ksh getopts function for this since it's a lot
# easier than checking them manually.

while getopts $options option
do
    case $option in
        A)
            allocstop="$OPTARG"
            ;;
        a)
            allocbyte="$OPTARG"
            ;;
        C)
            check="$OPTARG"
            ;;
        c)
            checkall="true";
            ;;
        D)
            defalign="$OPTARG"
            ;;
        d)
            preload="true"
            ;;
        e)
            progfile="$OPTARG"
            ;;
        F)
            freestop="$OPTARG"
            ;;
        f)
            freebyte="$OPTARG"
            ;;
        G)
            safesignals="true"
            ;;
        g)
            usedebug="true"
            ;;
        L)
            limit="$OPTARG"
            ;;
        l)
            logfile="$OPTARG"
            ;;
        m)
            usemmap="true"
            ;;
        N)
            noprotect="true"
            ;;
        n)
            nofree="true"
            ;;
        O)
            oflowsize="$OPTARG"
            ;;
        o)
            oflowbyte="$OPTARG"
            ;;
        P)
            pagealloc="UPPER"
            ;;
        p)
            pagealloc="LOWER"
            ;;
        R)
            reallocstop="$OPTARG"
            ;;
        S)
            showmap="true"
            ;;
        s)
            showfreed="true"
            ;;
        U)
            unfreedabort="$OPTARG"
            ;;
        v)
            preserve="true"
            ;;
        w)
            oflowwatch="true"
            ;;
        Z)
            failseed="$OPTARG"
            ;;
        z)
            failfreq="$OPTARG"
            ;;
        \?)
            usage
            exit 1
            ;;
    esac
done


# If no command was specified then we just exit.

shift `expr $OPTIND - 1`
if [ $# = 0 ]
then
    usage
    exit 1
fi


# Now build the environment variable containing library options,
# which automatically includes the LOGALL option by default.
# The environment variable may already have been set when this
# script is invoked, so we just append options to the end of it.

if [ "$MPATROL_OPTIONS" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS LOGALL"
else
    MPATROL_OPTIONS="LOGALL"
fi
if [ "$allocbyte" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS ALLOCBYTE=\"$allocbyte\""
fi
if [ "$allocstop" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS ALLOCSTOP=\"$allocstop\""
fi
if [ "$check" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS CHECK=\"$check\""
fi
if [ "$checkall" = "true" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS CHECKALL"
fi
if [ "$defalign" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS DEFALIGN=\"$defalign\""
fi
if [ "$failfreq" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS FAILFREQ=\"$failfreq\""
fi
if [ "$failseed" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS FAILSEED=\"$failseed\""
fi
if [ "$freebyte" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS FREEBYTE=\"$freebyte\""
fi
if [ "$freestop" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS FREESTOP=\"$freestop\""
fi
if [ "$limit" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS LIMIT=\"$limit\""
fi
if [ "$logfile" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS LOGFILE=\"$logfile\""
fi
if [ "$nofree" = "true" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS NOFREE"
fi
if [ "$noprotect" = "true" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS NOPROTECT"
fi
if [ "$oflowbyte" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS OFLOWBYTE=\"$oflowbyte\""
fi
if [ "$oflowsize" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS OFLOWSIZE=\"$oflowsize\""
fi
if [ "$oflowwatch" = "true" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS OFLOWWATCH"
fi
if [ "$pagealloc" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS PAGEALLOC=\"$pagealloc\""
fi
if [ "$preserve" = "true" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS PRESERVE"
fi
if [ "$progfile" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS PROGFILE=\"$progfile\""
fi
if [ "$reallocstop" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS REALLOCSTOP=\"$reallocstop\""
fi
if [ "$safesignals" = "true" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS SAFESIGNALS"
fi
if [ "$showmap" = "true" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS SHOWMAP SHOWSYMBOLS"
fi
if [ "$showfreed" = "true" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS SHOWFREED SHOWUNFREED"
fi
if [ "$unfreedabort" != "" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS UNFREEDABORT=\"$unfreedabort\""
fi
if [ "$usedebug" = "true" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS USEDEBUG"
fi
if [ "$usemmap" = "true" ]
then
    MPATROL_OPTIONS="$MPATROL_OPTIONS USEMMAP"
fi
export MPATROL_OPTIONS


# The dynamic linker on some UNIX systems supports requests for it to preload
# a specified list of shared libraries before running a process, via the
# LD_PRELOAD environment variable.

if [ "$preload" = "true" ]
then
    # The following shared libraries should exist in LD_LIBRARY_PATH if they
    # are to be used, otherwise their full path should be given.

    shared_libs="libmpatrol.so"


    # If any of the following libraries only exist as archive libraries then
    # the mpatrol library should be built to contain them since there is no
    # way to preload an archive library.

    coff_libs=""
    elf_libs="libelf.so"
    bfd_libs="libbfd.so libiberty.so"


    # Add any object file format-specific shared libraries.  The format
    # variable is set at the top of this file and should match the format
    # that the mpatrol library was built to support.

    if [ "$format" = "coff" ]
    then
        shared_libs="$shared_libs $coff_libs"
    elif [ "$format" = "elf" ]
    then
        shared_libs="$shared_libs $elf_libs"
    elif [ "$format" = "bfd" ]
    then
        shared_libs="$shared_libs $bfd_libs"
    else
        echo "$command: unknown object file format: $format" >&2
        exit 1
    fi


    # Set the LD_PRELOAD environment variable.  If this particular UNIX
    # system does not support this feature then doing this will have no
    # effect when running the command to be tested, but at least it won't
    # have any adverse effects.

    LD_PRELOAD="$shared_libs"
    export LD_PRELOAD
fi


# Run the command to be tested.  We do an exec here since we don't need to run
# any commands after this, and we'd like to preserve the original exit value.

exec "$@"
