Mon Feb 18 14:37:17 CET 2002 Paolo Molaro <lupus@ximian.com>
authorPaolo Molaro <lupus@oddwiz.org>
Tue, 19 Feb 2002 06:36:01 +0000 (06:36 -0000)
committerPaolo Molaro <lupus@oddwiz.org>
Tue, 19 Feb 2002 06:36:01 +0000 (06:36 -0000)
* monograph.c: First commit: program to generate call graphs
or type hierarchies from CIL programs.

svn path=/trunk/mono/; revision=2496

configure.in
mono/Makefile.am
mono/monograph/ChangeLog [new file with mode: 0644]
mono/monograph/Makefile.am [new file with mode: 0644]
mono/monograph/monograph.c [new file with mode: 0644]

index 0bc1af3d8bf6a1149533b45e84591cc15d5365e8..ebeb9c6a6191aab56d6da90b3ee1aa30912a209b 100644 (file)
@@ -386,6 +386,7 @@ mono/interpreter/Makefile
 mono/tests/Makefile
 mono/wrapper/Makefile
 mono/monoburg/Makefile
+mono/monograph/Makefile
 mono/jit/Makefile
 mono/io-layer/Makefile
 runtime/Makefile
index 50227f6916b4ae99cd42ccf0af5e43411b43abb9..b7ef587aa283633c7d4e6174a93bcbe579ad1437 100644 (file)
@@ -1,2 +1,3 @@
-SUBDIRS = io-layer monoburg metadata cil dis arch interpreter jit wrapper tests
+SUBDIRS = io-layer monoburg metadata cil dis \
+       arch monograph interpreter jit wrapper tests
 
diff --git a/mono/monograph/ChangeLog b/mono/monograph/ChangeLog
new file mode 100644 (file)
index 0000000..f8af167
--- /dev/null
@@ -0,0 +1,6 @@
+
+Mon Feb 18 14:37:17 CET 2002 Paolo Molaro <lupus@ximian.com>
+
+       * monograph.c: First commit: program to generate call graphs
+       or type hierarchies from CIL programs.
+
diff --git a/mono/monograph/Makefile.am b/mono/monograph/Makefile.am
new file mode 100644 (file)
index 0000000..7ab0c74
--- /dev/null
@@ -0,0 +1,26 @@
+
+bin_PROGRAMS = monograph
+
+INCLUDES =                             \
+       -I$(top_srcdir)                 \
+       $(GMODULE_CFLAGS)               \
+       $(GLIB_CFLAGS)
+
+monograph_LDADD = \
+       ../metadata/libmetadata.a       \
+       ../io-layer/libwapi.a           \
+       $(GLIB_LIBS)                    \
+       $(GMODULE_LIBS)                 \
+       -lm
+
+GRAPHS=System.Object System.Enum System.Attribute System.ValueType System.Reflection.MemberInfo
+OUT=$(GRAPHS:=.jpeg)
+
+graphs: $(OUT)
+
+%.jpeg: monograph
+       ./monograph -n -o $*.png corlib.dll $*
+       convert -geometry '480x360>' $*.png $*.jpeg
+
+EXTRA_DIST = ChangeLog
+
diff --git a/mono/monograph/monograph.c b/mono/monograph/monograph.c
new file mode 100644 (file)
index 0000000..4a9f142
--- /dev/null
@@ -0,0 +1,486 @@
+#include <glib.h>
+#include <string.h>
+#include "mono/metadata/class.h"
+#include "mono/metadata/assembly.h"
+#include "mono/metadata/tokentype.h"
+#include "mono/metadata/opcodes.h"
+#include "mono/metadata/tabledefs.h"
+#include "mono/metadata/cil-coff.h" /* MonoCLIImageInfo */
+#include "mono/metadata/mono-endian.h"
+#include "mono/metadata/appdomain.h" /* mono_init */
+
+static FILE *output;
+static int include_namespace = 0;
+static int max_depth = 6;
+static int verbose = 0;
+static char *graph_properties = "\tnode [fontsize=8.0]\n\tedge [len=2,color=red]\n";
+
+static void
+output_type_edge (MonoClass *first, MonoClass *second) {
+       if (include_namespace)
+               fprintf (output, "\t\"%s.%s\" -> \"%s.%s\"\n", first->name_space, first->name, second->name_space, second->name);
+       else
+               fprintf (output, "\t\"%s\" -> \"%s\"\n", first->name, second->name);
+}
+
+static void
+print_subtypes (MonoImage *image, MonoClass *class, int depth) {
+       int i, index;
+       MonoTableInfo *t;
+       MonoClass *child;
+
+       if (depth++ > max_depth)
+               return;
+
+       t = &image->tables [MONO_TABLE_TYPEDEF];
+       
+       index = mono_metadata_token_index (class->type_token);
+       index <<= TYPEDEFORREF_BITS;
+       index |= TYPEDEFORREF_TYPEDEF;
+
+       /* use a subgraph? */
+       for (i = 0; i < t->rows; ++i) {
+               if (index == mono_metadata_decode_row_col (t, i, MONO_TYPEDEF_EXTENDS)) {
+                       child = mono_class_get (image, MONO_TOKEN_TYPE_DEF | (i + 1));
+                       output_type_edge (class, child);
+                       print_subtypes (image, child, depth);
+               }
+       }
+}
+
+static void
+type_graph (MonoImage *image, char* cname) {
+       MonoClass *class;
+       MonoClass *parent;
+       MonoClass *child;
+       char *name_space;
+       char *p;
+       int depth = 0;
+
+       cname = g_strdup (cname);
+       p = strrchr (cname, '.');
+       if (p) {
+               name_space = cname;
+               *p++ = 0;
+               cname = p;
+       } else {
+               name_space = "";
+       }
+       class = mono_class_from_name (image, name_space, cname);
+       if (!class)
+               g_error ("class %s.%s not found", name_space, cname);
+       fprintf (output, "digraph blah {\n");
+       fprintf (output, "%s", graph_properties);
+       child = class;
+       /* go back and print the parents for the node as well: not sure it's a good idea */
+       for (parent = class->parent; parent; parent = parent->parent) {
+               output_type_edge (parent, child);
+               child = parent;
+       }
+       print_subtypes (image, class, depth);
+       fprintf (output, "}\n");
+}
+
+static void
+get_type (GString *res, MonoType *type) {
+       switch (type->type) {
+       case MONO_TYPE_VOID:
+               g_string_append (res, "void"); break;
+       case MONO_TYPE_CHAR:
+               g_string_append (res, "char"); break;
+       case MONO_TYPE_BOOLEAN:
+               g_string_append (res, "bool"); break;
+       case MONO_TYPE_U1:
+               g_string_append (res, "byte"); break;
+       case MONO_TYPE_I1:
+               g_string_append (res, "sbyte"); break;
+       case MONO_TYPE_U2:
+               g_string_append (res, "uint16"); break;
+       case MONO_TYPE_I2:
+               g_string_append (res, "int16"); break;
+       case MONO_TYPE_U4:
+               g_string_append (res, "int"); break;
+       case MONO_TYPE_I4:
+               g_string_append (res, "unint"); break;
+       case MONO_TYPE_U8:
+               g_string_append (res, "ulong"); break;
+       case MONO_TYPE_I8:
+               g_string_append (res, "long"); break;
+       case MONO_TYPE_FNPTR: /* who cares for the exact signature? */
+               g_string_append (res, "*()"); break;
+       case MONO_TYPE_U:
+               g_string_append (res, "intptr"); break;
+       case MONO_TYPE_I:
+               g_string_append (res, "unintptr"); break;
+       case MONO_TYPE_R4:
+               g_string_append (res, "single"); break;
+       case MONO_TYPE_R8:
+               g_string_append (res, "double"); break;
+       case MONO_TYPE_STRING:
+               g_string_append (res, "string"); break;
+       case MONO_TYPE_OBJECT:
+               g_string_append (res, "object"); break;
+       case MONO_TYPE_PTR:
+               get_type (res, type->data.type);
+               g_string_append_c (res, '*');
+               break;
+       case MONO_TYPE_ARRAY:
+               get_type (res, type->data.array->type);
+               g_string_append (res, "[,]"); /* not the full array info.. */
+               break;
+       case MONO_TYPE_SZARRAY:
+               get_type (res, type->data.type);
+               g_string_append (res, "[]");
+               break;
+       case MONO_TYPE_CLASS:
+       case MONO_TYPE_VALUETYPE: {
+               MonoClass *class = type->data.klass;
+               if (!class) {
+                       g_string_append (res, "Unknown");
+                       break;
+               }
+               if (include_namespace && *(class->name_space))
+                       g_string_sprintfa (res, "%s.", class->name_space);
+               g_string_sprintfa (res, "%s", class->name);
+               break;
+       }
+       default:
+               break;
+       }
+       if (type->byref)
+               g_string_append_c (res, '&');
+}
+
+static char *
+get_signature (MonoMethod *method) {
+       GString *res;
+       static GHashTable *hash = NULL;
+       char *result;
+       int i;
+
+       if (!hash)
+               hash = g_hash_table_new (g_direct_hash, g_direct_equal);
+       if ((result = g_hash_table_lookup (hash, method)))
+               return result;
+
+       res = g_string_new ("");
+       if (include_namespace && *(method->klass->name_space))
+               g_string_sprintfa (res, "%s.", method->klass->name_space);
+       g_string_sprintfa (res, "%s:%s(", method->klass->name, method->name);
+       for (i = 0; i < method->signature->param_count; ++i) {
+               if (i > 0)
+                       g_string_append_c (res, ',');
+               get_type (res, method->signature->params [i]);
+       }
+       g_string_sprintfa (res, ")");
+       g_hash_table_insert (hash, method, res->str);
+
+       result = res->str;
+       g_string_free (res, FALSE);
+       return result;
+               
+}
+
+static void
+output_method_edge (MonoMethod *first, MonoMethod *second) {
+       char * f = get_signature (first);
+       char * s = get_signature (second);
+       
+       fprintf (output, "\t\"%s\" -> \"%s\"\n", f, s);
+}
+
+/*
+ * We need to handle virtual calls is derived types.
+ * We could check what types implement the method and
+ * disassemble all of them: this can make the graph to explode.
+ * We could try and keep track of the 'this' pointer type and
+ * consider only the methods in the classes derived from that:
+ * this can reduce the graph complexity somewhat (and it would 
+ * be the more correct approach).
+ */
+static void
+print_method (MonoMethod *method, int depth) {
+       const MonoOpcode *opcode;
+       MonoMethodHeader *header;
+       GHashTable *hash;
+       const unsigned char *ip;
+       int i;
+
+       if (depth++ > max_depth)
+               return;
+       if (method->info) /* avoid recursion */
+               return;
+       method->info = method;
+
+       if (method->iflags & (METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL | METHOD_IMPL_ATTRIBUTE_RUNTIME))
+               return;
+       if (method->flags & (METHOD_ATTRIBUTE_PINVOKE_IMPL | METHOD_ATTRIBUTE_ABSTRACT))
+               return;
+
+       header = ((MonoMethodNormal *)method)->header;
+       ip = header->code;
+
+       hash = g_hash_table_new (g_direct_hash, g_direct_equal);
+       
+       while (ip < (header->code + header->code_size)) {
+               if (*ip == 0xfe) {
+                       ++ip;
+                       i = *ip + 256;
+               } else {
+                       i = *ip;
+               }
+
+               opcode = &mono_opcodes [i];
+
+               switch (opcode->argument) {
+               case MonoInlineNone:
+                       ++ip;
+                       break;
+               case MonoInlineType:
+               case MonoInlineField:
+               case MonoInlineTok:
+               case MonoInlineString:
+               case MonoInlineSig:
+               case MonoShortInlineR:
+               case MonoInlineI:
+               case MonoInlineBrTarget:
+                       ip += 5;
+                       break;
+               case MonoInlineVar:
+                       ip += 3;
+                       break;
+               case MonoShortInlineVar:
+               case MonoShortInlineI:
+               case MonoShortInlineBrTarget:
+                       ip += 2;
+                       break;
+               case MonoInlineSwitch: {
+                       gint32 n;
+                       ++ip;
+                       n = read32 (ip);
+                       ip += 4;
+                       ip += 4 * n;
+                       break;
+               }
+               case MonoInlineR:
+               case MonoInlineI8:
+                       ip += 9;
+                       break;
+               case MonoInlineMethod: {
+                       guint32 token;
+                       MonoMethod *called;
+                       ip++;
+                       token = read32 (ip);
+                       ip += 4;
+                       called = mono_get_method (method->klass->image, token, NULL);
+                       if (!called)
+                               break; /* warn? */
+                       if (g_hash_table_lookup (hash, called))
+                               break;
+                       g_hash_table_insert (hash, called, called);
+                       output_method_edge (method, called);
+                       print_method (called, depth);
+                       break;
+               }
+               default:
+                       g_assert_not_reached ();
+               }
+       }
+       g_hash_table_destroy (hash);
+}
+
+static void
+method_graph (MonoImage *image, char *name) {
+       int depth = 0;
+       MonoMethod *method = NULL;
+       
+       if (!name) {
+               method = mono_get_method (image, ((MonoCLIImageInfo*)image->image_info)->cli_cli_header.ch_entry_point, NULL);
+               if (!method)
+                       g_error ("Cannot find entry point");
+       } else {
+               /* search the method */
+               MonoTableInfo *tdef = &image->tables [MONO_TABLE_TYPEDEF];
+               MonoTableInfo *methods = &image->tables [MONO_TABLE_METHOD];
+               char *class_name, *class_nspace, *method_name, *use_args;
+               int use_namespace, i;
+               
+               class_nspace = g_strdup (name);
+               use_args = strchr (class_nspace, '(');
+               if (use_args)
+                       *use_args++ = 0;
+               method_name = strrchr (class_nspace, ':');
+               if (!method_name)
+                       g_error ("Invalid method name %s", name);
+               *method_name++ = 0;
+               class_name = strrchr (class_nspace, '.');
+               if (class_name) {
+                       *class_name++ = 0;
+                       use_namespace = 1;
+               } else {
+                       class_name = class_nspace;
+                       use_namespace = 0;
+               }
+               for (i = 0; i < methods->rows; ++i) {
+                       guint32 index = mono_metadata_decode_row_col (methods, i, MONO_METHOD_NAME);
+                       guint32 idx;
+                       const char *n = mono_metadata_string_heap (image, index);
+
+                       if (strcmp (n, method_name))
+                               continue;
+                       index = mono_metadata_typedef_from_method (image, i + 1);
+                       idx = mono_metadata_decode_row_col (tdef, index - 1, MONO_TYPEDEF_NAME);
+                       n = mono_metadata_string_heap (image, idx);
+                       if (strcmp (n, class_name))
+                               continue;
+                       if (use_namespace) {
+                               idx = mono_metadata_decode_row_col (tdef, index - 1, MONO_TYPEDEF_NAMESPACE);
+                               n = mono_metadata_string_heap (image, idx);
+                               if (strcmp (n, class_nspace))
+                                       continue;
+                       }
+                       method = mono_get_method (image, MONO_TOKEN_METHOD_DEF | (i + 1), NULL);
+                       if (use_args) {
+                               /* check the signature */
+                               n = get_signature (method);
+                               if (strcmp (n, name) == 0)
+                                       break;
+                               if (verbose)
+                                       g_print ("signature check failed: '%s' != '%s'.\n", n, name);
+                               method = NULL;
+                       }
+               }
+               if (!method)
+                       g_error ("Cannot find method %s", name);
+               g_free (class_nspace);
+       }
+       fprintf (output, "digraph blah {\n");
+       fprintf (output, "%s", graph_properties);
+
+       print_method (method, depth);
+       
+       fprintf (output, "}\n");
+}
+
+static void
+usage () {
+       printf ("monograph 0.1 Copyright (c) 2002 Ximian, Inc\n");
+       printf ("Create call graph or type hierarchy information from CIL assemblies.\n");
+       printf ("Usage: monograph [options] [assembly [typename|methodname]]\n");
+       printf ("Valid options are:\n");
+       printf ("\t-c|--call             output call graph instead of type hierarchy\n");
+       printf ("\t-d|--depth num        max depth recursion (default: 6)\n");
+       printf ("\t-o|--output filename  write graph to file filename (default: stdout)\n");
+       printf ("\t-f|--fullname         include namespace in type and method names\n");
+       printf ("\t-n|--neato            invoke neato directly\n");
+       printf ("\t-v|--verbose          verbose operation\n");
+       printf ("The default assembly is corlib.dll. The default method for\n");
+       printf ("the --call option is the entrypoint.\n");
+       printf ("When the --neato option is used the output type info is taken\n");
+       printf ("from the output filename extension.\n");
+       printf ("Sample runs:\n");
+       printf ("\tmonograph -n -o vt.png corlib.dll System.ValueType\n");
+       printf ("\tmonograph -n -o expr.png mcs.exe Mono.CSharp.Expression\n");
+       printf ("\tmonograph -d 3 -n -o callgraph.png -c mis.exe\n");
+       exit (1);
+}
+
+/*
+ * TODO:
+ * * virtual method calls as explained above
+ * * maybe track field references
+ * * track what exceptions a method could throw?
+ * * for some inputs neato appears to hang or take a long time: kill it?
+ * * allow passing additional command-line arguments to neato
+ * * allow setting different graph/node/edge options directly
+ * * option to avoid specialname methods
+ * * make --neato option the default?
+ * * use multiple classes/method roots?
+ * * write a manpage
+ */
+int
+main (int argc, char *argv[]) {
+       MonoAssembly *assembly;
+       char *cname = NULL;
+       char *aname = NULL;
+       char *outputfile = NULL;
+       int callgraph = 0;
+       int callneato = 0;
+       int i;
+       
+       mono_init ();
+       output = stdout;
+
+       for (i = 1; i < argc; ++i) {
+               if (argv [i] [0] != '-')
+                       break;
+               if (strcmp (argv [i], "--call") == 0 || strcmp (argv [i], "-c") == 0) {
+                       callgraph = 1;
+               } else if (strcmp (argv [i], "--fullname") == 0 || strcmp (argv [i], "-f") == 0) {
+                       include_namespace = 1;
+               } else if (strcmp (argv [i], "--neato") == 0 || strcmp (argv [i], "-n") == 0) {
+                       callneato = 1;
+               } else if (strcmp (argv [i], "--verbose") == 0 || strcmp (argv [i], "-v") == 0) {
+                       verbose++;
+               } else if (strcmp (argv [i], "--output") == 0 || strcmp (argv [i], "-o") == 0) {
+                       if (i + 1 >= argc)
+                               usage ();
+                       outputfile = argv [++i];
+               } else if (strcmp (argv [i], "--depth") == 0 || strcmp (argv [i], "-d") == 0) {
+                       if (i + 1 >= argc)
+                               usage ();
+                       max_depth = atoi (argv [++i]);
+               } else {
+                       usage ();
+               }
+               
+       }
+       if (argc > i)
+               aname = argv [i];
+       if (argc > i + 1)
+               cname = argv [i + 1];
+       if (!aname)
+               aname = "corlib.dll";
+       if (!cname && !callgraph)
+               cname = "System.Object";
+
+       assembly = mono_assembly_open (aname, NULL, NULL);
+       if (!assembly)
+               g_error ("cannot open assembly %s", aname);
+
+       if (callneato) {
+               GString *command = g_string_new ("neato");
+               char *type = NULL;
+
+               if (outputfile) {
+                       type = strrchr (outputfile, '.');
+                       g_string_sprintfa (command, " -o %s", outputfile);
+               }
+               if (type)
+                       g_string_sprintfa (command, " -T%s", type + 1);
+               output = popen (command->str, "w");
+               if (!output)
+                       g_error ("Cannot run neato");
+       } else if (outputfile) {
+               output = fopen (outputfile, "w");
+               if (!output)
+                       g_error ("Cannot open file: %s", outputfile);
+       }
+       /* if it looks like a method name, we want a callgraph. */
+       if (cname && strchr (cname, ':'))
+               callgraph = 1;
+
+       if (callgraph)
+               method_graph (assembly->image, cname);
+       else
+               type_graph (assembly->image, cname);
+       if (callneato) {
+               if (verbose)
+                       g_print ("waiting for neato.\n");
+               pclose (output);
+       } else if (outputfile)
+               fclose (output);
+       return 0;
+}
+
+