#!/usr/local/bin/bash

# Copyright 2013 Dominik Seichter <domseichter@googlemail.com>
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of 
# the License, or (at your option) any later version.
# 
# This program 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 General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
# This is the maximum depth to which dependencies are resolved
MAXDEPTH=14

TMPFILE=
READELFTMPFILE=
LDDTMPFILE=

cleanup() {
	for _v in TMPFILE READELFTMPFILE LDDTMPFILE; do
		[ -n "${!_v}" ] && rm -f -- "${!_v}"
	done
}

trap cleanup EXIT
 
# analyze a given file on its
# dependecies using ldd and write
# the results to a given temporary file
#
# Usage: analyze [OUTPUTFILE] [INPUTFILE]
analyze()
{
    local OUT="$1"
    local IN="$2"
    local NAME=$(basename "$IN")
 
    for i in $LIST
    do
        if [ X"$i" == X"$NAME" ];
        then
            # This file was already parsed
            return
        fi
    done
    # Put the file in the list of all files
    LIST="$LIST $NAME"
 
    DEPTH=$(($DEPTH + 1))
    if [ $DEPTH -ge $MAXDEPTH ];
        then
        echo "MAXDEPTH of $MAXDEPTH reached at file $IN."
        echo "Continuing with next file..."
        DEPTH=$(($DEPTH - 1))
        return
    fi
 
    echo "Parsing file:              $IN"
 
    $READELF "$IN" > "$READELFTMPFILE" 2>&1
    ELFRET=$?
 
    if [ $ELFRET != 0 ];
        then
        echo "ERROR: ELF reader returned error code $RET"
        echo "ERROR:"
        cat -v -- "$TMPFILE"
        echo "Aborting..."
        exit 1
    fi
 
    DEPENDENCIES=$(cat "$READELFTMPFILE" | grep NEEDED | awk '{if (substr($NF,1,1) == "[") print substr($NF, 2, length($NF) - 2); else print $NF}')
 
    for DEP in $DEPENDENCIES;
    do
        if [ -n "$DEP" ];
        then
 
            # XXX Change ldd to something more secure?
            ldd "$IN" > "$LDDTMPFILE" 2>&1
            LDDRET=$?
 
            if [ $LDDRET != 0 ];
                then
                echo "ERROR: ldd returned error code $RET"
                echo "ERROR:"
                cat -v -- "$TMPFILE"
                echo "Aborting..."
                exit 1
            fi
 
            DEPPATH=$(grep -- "$DEP" "$LDDTMPFILE" | awk '{print $7}')
            if [ -n "$DEPPATH" ];
            then
                echo "  \"$NAME\" -> \"$DEP\";" >> $OUT
                analyze "$OUT" "$DEPPATH"
            fi
        fi
    done
 
    DEPTH=$(($DEPTH - 1))
}
 
########################################
# main                                 #
########################################
 
if [ $# != 2 ];
    then
    echo "Usage:"
    echo "  $0 [filename] [outputimage]"
    echo ""
    echo "This tools analyses a shared library or an executable"
    echo "and generates a dependency graph as an image."
    echo ""
    echo "GraphViz must be installed for this tool to work."
    echo ""
    exit 1
fi
 
DEPTH=0
INPUT="$1"
OUTPUT="$2"
TMPFILE="$(mktemp -t)"
LDDTMPFILE="$(mktemp -t)"
READELFTMPFILE="$(mktemp -t)"
LIST=""
 
if [ ! -e "$INPUT" ];
    then
    echo "ERROR: File not found: $INPUT"
    echo "Aborting..."
    exit 2
fi
 
# Use either readelf or dump
# Linux has readelf, Solaris has dump
READELF=$(type readelf 2> /dev/null)
if [ $? != 0 ]; then
  READELF=$(type dump 2> /dev/null)
  if [ $? != 0 ]; then
    echo Unable to find ELF reader
    exit 1
  fi
  READELF="dump -Lv"
else
  READELF="readelf -d"
fi
 
 
 
echo "Analyzing dependencies of: $INPUT"
echo "Creating output as:        $OUTPUT"
echo ""
 
echo "digraph DependencyTree {" > "$TMPFILE"
echo "  \"$(basename $INPUT)\" [shape=box];" >> "$TMPFILE"
analyze "$TMPFILE" "$INPUT"
echo "}" >> "$TMPFILE"
 
#cat $TMPFILE # output generated dotfile for debugging purposses
dot -Tpng "$TMPFILE" "-o$OUTPUT"
 
exit 0
