commit a600f2ed91ca4f369fb22fa242dc7e48ef40567b
Author: sin <sin@2f30.org>
Date: Wed, 17 Jul 2013 15:16:06 +0100
Initial commit
Diffstat:
A | COPYING | | | 14 | ++++++++++++++ |
A | Makefile | | | 17 | +++++++++++++++++ |
A | README | | | 29 | +++++++++++++++++++++++++++++ |
A | callgraph.c | | | 197 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | 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