callgraph

runtime callgraph generator
git clone git://git.2f30.org/callgraph.git
Log | Files | Refs | README

commit a600f2ed91ca4f369fb22fa242dc7e48ef40567b
Author: sin <sin@2f30.org>
Date:   Wed Jul 17 15:16:06 +0100

Initial commit

Diffstat:
COPYING | 14++++++++++++++
Makefile | 17+++++++++++++++++
README | 29+++++++++++++++++++++++++++++
callgraph.c | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
symbolize.sh | 34++++++++++++++++++++++++++++++++++
5 files changed, 291 insertions(+), 0 deletions(-)
diff --git a/COPYING b/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + 14 rue de Plaisance, 75014 Paris, France + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/Makefile b/Makefile @@ -0,0 +1,17 @@ +SRC = callgraph.c +OBJ = ${SRC:.c=.o} +SONAME = ${SRC:.c=.so} +CC = gcc + +all: callgraph + +.c.o: + ${CC} -fPIC -Wall -Wextra -std=gnu99 -Wshadow -Wunreachable-code -g -c ${SRC} + +callgraph: ${OBJ} + ${CC} -g -shared -Wl,-soname,${SONAME} -o ${SONAME} ${OBJ} -lc + +clean: + @rm -f ${OBJ} ${SONAME} callgraph.dot* + +.PHONY: all clean diff --git a/README b/README @@ -0,0 +1,29 @@ +Runtime callgraph generator +=========================== + +First type make to create callgraph.so. + +To generate a runtime callgraph of any program, compile it by adding the +-finstrument-functions flag in CFLAGS. + + ./configure # if needed + . # other crap + make CFLAGS+=-finstrument-functions + +Set the environment variable CGRAPH_OUT to the name of the .dot file. + + export CGRAPH_OUT=callgraph.dot + +At this point you can use LD_PRELOAD to create the callgraph of the program. + + LD_PRELOAD=./callgraph.so ./prog [ARGS] + +Once the program terminates, you have to feed it to the symbolizer. + + ./symbolize.sh ./prog + +Generate a jpeg of the callgraph. + + dot -Tjpg callgraph.dot -o callgraph.jpg + +That's it! diff --git a/callgraph.c b/callgraph.c @@ -0,0 +1,197 @@ +/* runtime callgraph generator by stateless */ + +#include <stdio.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <stdint.h> +#include <inttypes.h> +#include <assert.h> + +struct fnode_t { + uint64_t addr; + struct fnode_t *next; +}; + +struct fpair_t { + uint64_t l, r; /* l is the caller, r is the callee */ + struct fpair_t *next; +}; + +/* essentially a stack of function calls */ +static struct fnode_t *fnode_head = NULL; +static struct fnode_t *fnode_tail = NULL; + +/* a list of function pairs (caller -> callee) */ +static struct fpair_t *fpair_head = NULL; +static struct fpair_t *fpair_tail = NULL; + +static int fd = -1; +static char logbuf[BUFSIZ]; + +static void * +xmalloc(size_t size) +{ + void *p; + + p = malloc(size); + if (!p) + _Exit(EXIT_FAILURE); + return p; +} + +static ssize_t +writelog(int fildes, const void *buf, size_t nbyte) +{ + ssize_t w, r, tmp; + + w = 0; + r = nbyte; + if (r < 0) return 0; + do { +again: + tmp = write(fildes, (const char *)buf + w, r); + if (tmp <= 0) { + if (tmp) + if (errno == EINTR) + goto again; + return tmp; + } + w += tmp; + r -= tmp; + } while (r > 0); + return w; +} + +__attribute__ ((constructor)) static void +init_logging(void) +{ + char *f; + + f = getenv("CGRAPH_OUT"); + if (!f) { + fprintf(stderr, "cgraph: CGRAPH_OUT is not set!\n"); + _Exit(EXIT_FAILURE); + } + + fd = open(f, O_WRONLY | O_CREAT, 0644); + if (fd < 0 || ftruncate(fd, 0) < 0) { + perror("cgraph"); + _Exit(EXIT_FAILURE); + } +} + +__attribute__ ((destructor)) static void +deinit_logging(void) +{ + struct fpair_t *p, *tmppair; + struct fnode_t *n, *tmpnode; + + writelog(fd, "digraph G {\n", + strlen("digraph G {\n")); + p = fpair_head; + while (p) { + snprintf(logbuf, sizeof logbuf, + "\t \"%"PRIx64"\" -> \"%"PRIx64"\"\n", p->l, p->r); + writelog(fd, logbuf, strlen(logbuf)); + p = p->next; + } + writelog(fd, "}\n", 2); + + if (fd != -1) + if (close(fd) < 0) + perror("cgraph"); + + p = fpair_head; + while (p) { + tmppair = p->next; + free(p); + p = tmppair; + } + n = fnode_head; + while (n) { + tmpnode = n->next; + free(n); + n = tmpnode; + } +} + +static void +push_fnode(uint64_t addr) +{ + if (!fnode_head) { + fnode_head = xmalloc(sizeof(struct fnode_t)); + fnode_head->addr = addr; + fnode_head->next = NULL; + fnode_tail = fnode_head; + } else { + fnode_tail->next = xmalloc(sizeof(struct fnode_t)); + fnode_tail = fnode_tail->next; + fnode_tail->next = NULL; + fnode_tail->addr = addr; + } +} + +static void +pop_fnode(void) +{ + struct fnode_t *i; + + assert(fnode_tail); + for (i = fnode_head; i; i = i->next) + if (i->next == fnode_tail) + break; + if (!i) return; + free(fnode_tail); + fnode_tail = i; + fnode_tail->next = NULL; +} + +static inline struct fnode_t * +get_top(void) { + return fnode_tail; +} + +static void +add_fpair(uint64_t r) +{ + struct fnode_t *curfun; + + curfun = get_top(); + if (!fpair_head) { + fpair_head = xmalloc(sizeof(struct fpair_t)); + fpair_head->l = curfun->addr; + fpair_head->r = r; + fpair_head->next = NULL; + fpair_tail = fpair_head; + } else { + fpair_tail->next = xmalloc(sizeof(struct fpair_t)); + fpair_tail = fpair_tail->next; + fpair_tail->l = curfun->addr; + fpair_tail->r = r; + fpair_tail->next = NULL; + } +} + +void +__cyg_profile_func_enter(void *func_address, + __attribute__ ((unused)) void *call_site) +{ + struct fnode_t *n; + + n = get_top(); + if (n) add_fpair((uint64_t)func_address); + push_fnode((uint64_t)func_address); +} + +void +__cyg_profile_func_exit ( + __attribute__ ((unused)) void *func_address, + __attribute__ ((unused)) void *call_site) +{ + pop_fnode(); +} + diff --git a/symbolize.sh b/symbolize.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# parse the .dot file and translate +# addresses to symbol names. + +if [ ! -n "$1" ]; then + echo "Usage: `basename $0` bin" 1>&2 + exit 1 +fi + +env | grep CGRAPH_OUT= &>/dev/null +if [ ! "$?" -eq 0 -o "$CGRAPH_OUT" = "" ]; then + echo "CGRAPH_OUT is not set!" 1>&2 + exit 1 +fi + +# remove duplicate function calls +echo "digraph G {" > $CGRAPH_OUT.tmp +grep '\->' $CGRAPH_OUT | sort | uniq >> $CGRAPH_OUT.tmp +echo "}" >> $CGRAPH_OUT.tmp +mv $CGRAPH_OUT.tmp $CGRAPH_OUT + +# do the translation +echo "digraph G {" > $CGRAPH_OUT.tmp +for i in `grep '\->' $CGRAPH_OUT | sed 's/[ \t]*//g' \ + | sed 's/"//g' | sed 's/\->/:/g'`; do + l=`echo $i | awk -F: '{print $1}'` + r=`echo $i | awk -F: '{print $2}'` + # could use addr2line here + l=`nm $1 | grep $l | awk '{print $3}'` + r=`nm $1 | grep $r | awk '{print $3}'` + echo -e "\t$l -> $r" >> $CGRAPH_OUT.tmp +done +echo "}" >> $CGRAPH_OUT.tmp +mv $CGRAPH_OUT.tmp $CGRAPH_OUT