A few pretty print fixes for the GDB plugin. (#4001)
[mono.git] / data / gdb / mono-gdb.py
index 751b51aaaa27f152e01d600106c9d46f9f0cd0d4..43c49cfc6a0aad2e99147091f0fb76a1697cc37c 100644 (file)
@@ -4,17 +4,13 @@
 #
 
 #
-# This is a mono support mode for a python-enabled gdb:
-# http://sourceware.org/gdb/wiki/PythonGdb
+# This is a mono support mode for gdb 7.0 and later
 # Usage:
-# - copy/symlink this file, plus mono-gdbinit to the directory where the mono 
-#   executable lives.
-# - run mono under gdb, or attach to a mono process using gdb
-# - Type 'xdb' in gdb to load/reload the debugging info emitted by the runtime.
-# - The debug info is emitted to a file called xdb.s in the current working directory.
-#   When attaching to a mono process, make sure you are in the same directory.
+# - copy/symlink this file to the directory where the mono executable lives.
+# - run mono under gdb, or attach to a mono process started with --debug=gdb using gdb.
 #
 
+from __future__ import print_function
 import os
 
 class StringPrinter:
@@ -24,16 +20,20 @@ class StringPrinter:
         self.val = val
 
     def to_string(self):
-        if int(self.val.cast (gdb.Type ("guint64"))) == 0:
+        if int(self.val.cast (gdb.lookup_type ("guint64"))) == 0:
             return "null"
 
-        obj = self.val.cast (gdb.Type ("MonoString").pointer ()).dereference ()
+        obj = self.val.cast (gdb.lookup_type ("MonoString").pointer ()).dereference ()
         len = obj ['length']
         chars = obj ['chars']
         i = 0
         res = ['"']
         while i < len:
-            c = chr ((chars.cast(gdb.Type ("gint64")) + (i * 2)).cast(gdb.Type ("gunichar2").pointer ()).dereference ())
+            val = (chars.cast(gdb.lookup_type ("gint64")) + (i * 2)).cast(gdb.lookup_type ("gunichar2").pointer ()).dereference ()
+            if val >= 256:
+                c = unichr (val)
+            else:
+                c = chr (val)
             res.append (c)
             i = i + 1
         res.append ('"')
@@ -45,7 +45,10 @@ def stringify_class_name(ns, name):
             return "byte"
         if name == "String":
             return "string"
-    return "%s.%s" % (ns, name)
+    if ns == "":
+        return name
+    else:
+        return "{}.{}".format (ns, name)
 
 class ArrayPrinter:
     "Print a C# array"
@@ -56,20 +59,23 @@ class ArrayPrinter:
         self.class_name = class_name
 
     def to_string(self):
-        obj = self.val.cast (gdb.Type ("MonoArray").pointer ()).dereference ()
+        obj = self.val.cast (gdb.lookup_type ("MonoArray").pointer ()).dereference ()
         length = obj ['max_length']
-        return "%s [%d]" % (stringify_class_name (self.class_ns, self.class_name [0:len(self.class_name) - 2]), int(length))
+        return "{} [{}]".format (stringify_class_name (self.class_ns, self.class_name [0:len(self.class_name) - 2]), int(length))
         
 class ObjectPrinter:
     "Print a C# object"
 
     def __init__(self, val):
-        self.val = val
+        if str(val.type)[-1] == "&":
+            self.val = val.address.cast (gdb.lookup_type ("MonoObject").pointer ())
+        else:
+            self.val = val.cast (gdb.lookup_type ("MonoObject").pointer ())
 
     class _iterator:
         def __init__(self,obj):
             self.obj = obj
-            self.iter = self.obj.type ().fields ().__iter__ ()
+            self.iter = self.obj.type.fields ().__iter__ ()
             pass
 
         def __iter__(self):
@@ -77,27 +83,42 @@ class ObjectPrinter:
 
         def next(self):
             field = self.iter.next ()
-            return (field.name, self.obj [field.name])
+            try:
+                if str(self.obj [field.name].type) == "object":
+                    # Avoid recursion
+                    return (field.name, self.obj [field.name].cast (gdb.lookup_type ("void").pointer ()))
+                else:
+                    return (field.name, self.obj [field.name])
+            except:
+                # Superclass
+                return (field.name, self.obj.cast (gdb.lookup_type ("{}".format (field.name))))
 
     def children(self):
         # FIXME: It would be easier if gdb.Value would support iteration itself
         # It would also be better if we could return None
-        if int(self.val.cast (gdb.Type ("guint64"))) == 0:
+        if int(self.val.cast (gdb.lookup_type ("guint64"))) == 0:
             return {}.__iter__ ()
         try:
-            obj = self.val.cast (gdb.Type ("MonoObject").pointer ()).dereference ()
+            obj = self.val.dereference ()
             class_ns = obj ['vtable'].dereference ()['klass'].dereference ()['name_space'].string ()
             class_name = obj ['vtable'].dereference ()['klass'].dereference ()['name'].string ()
-            gdb_type = gdb.Type ("struct %s.%s" % (class_ns, class_name))
-            return self._iterator(obj.cast (gdb_type))
+            if class_name [-2:len(class_name)] == "[]":
+                return {}.__iter__ ()
+            try:
+                gdb_type = gdb.lookup_type ("struct {}_{}".format (class_ns.replace (".", "_"), class_name))
+                return self._iterator(obj.cast (gdb_type))
+            except:
+                return {}.__iter__ ()
         except:
+            print (sys.exc_info ()[0])
+            print (sys.exc_info ()[1])
             return {}.__iter__ ()
 
     def to_string(self):
-        if int(self.val.cast (gdb.Type ("guint64"))) == 0:
+        if int(self.val.cast (gdb.lookup_type ("guint64"))) == 0:
             return "null"
         try:
-            obj = self.val.cast (gdb.Type ("MonoObject").pointer ()).dereference ()
+            obj = self.val.dereference ()
             class_ns = obj ['vtable'].dereference ()['klass'].dereference ()['name_space'].string ()
             class_name = obj ['vtable'].dereference ()['klass'].dereference ()['name'].string ()
             if class_ns == "System" and class_name == "String":
@@ -106,24 +127,201 @@ class ObjectPrinter:
                 return ArrayPrinter (self.val,class_ns,class_name).to_string ()
             if class_ns != "":
                 try:
-                    gdb_type = gdb.Type ("struct %s.%s" % (class_ns, class_name))
+                    gdb_type = gdb.lookup_type ("struct {}.{}".format (class_ns, class_name))
                 except:
                     # Maybe there is no debug info for that type
-                    return "%s.%s" % (class_ns, class_name)
+                    return "{}.{}".format (class_ns, class_name)
                 #return obj.cast (gdb_type)
-                return "%s.%s" % (class_ns, class_name)
+                return "{}.{}".format (class_ns, class_name)
             return class_name
         except:
-            print sys.exc_info ()[0]
-            print sys.exc_info ()[1]
+            print (sys.exc_info ()[0])
+            print (sys.exc_info ()[1])
             # FIXME: This can happen because we don't have liveness information
-            return self.val.cast (gdb.Type ("guint64"))
+            return self.val.cast (gdb.lookup_type ("guint64"))
+        
+class MonoMethodPrinter:
+    "Print a MonoMethod structure"
+
+    def __init__(self, val):
+        self.val = val
+
+    def to_string(self):
+        if int(self.val.cast (gdb.lookup_type ("guint64"))) == 0:
+            return "0x0"
+        val = self.val.dereference ()
+        klass = val ["klass"].dereference ()
+        class_name = stringify_class_name (klass ["name_space"].string (), klass ["name"].string ())
+        return "\"{}:{} ()\"".format (class_name, val ["name"].string ())
+        # This returns more info but requires calling into the inferior
+        #return "\"{}\"".format (gdb.parse_and_eval ("mono_method_full_name ({}, 1)".format (str (int (self.val.cast (gdb.lookup_type ("guint64")))))).string ())
+
+class MonoClassPrinter:
+    "Print a MonoClass structure"
+
+    def __init__(self, val):
+        self.val = val
+
+    def to_string_inner(self, add_quotes):
+        if int(self.val.cast (gdb.lookup_type ("guint64"))) == 0:
+            return "0x0"
+        klass = self.val.dereference ()
+        class_name = stringify_class_name (klass ["name_space"].string (), klass ["name"].string ())
+        if add_quotes:
+            return "\"{}\"".format (class_name)
+        else:
+            return class_name
+        # This returns more info but requires calling into the inferior
+        #return "\"{}\"".format (gdb.parse_and_eval ("mono_type_full_name (&((MonoClass*){})->byval_arg)".format (str (int ((self.val).cast (gdb.lookup_type ("guint64")))))))
+
+    def to_string(self):
+        try:
+            return self.to_string_inner (True)
+        except:
+            #print (sys.exc_info ()[0])
+            #print (sys.exc_info ()[1])
+            return str(self.val.cast (gdb.lookup_type ("gpointer")))
+
+class MonoGenericInstPrinter:
+    "Print a MonoGenericInst structure"
+
+    def __init__(self, val):
+        self.val = val
+
+    def to_string(self):
+        if int(self.val.cast (gdb.lookup_type ("guint64"))) == 0:
+            return "0x0"
+        inst = self.val.dereference ()
+        inst_len = inst ["type_argc"]
+        inst_args = inst ["type_argv"]
+        inst_str = ""
+        for i in range(0, inst_len):
+            # print (inst_args)
+            type_printer = MonoTypePrinter (inst_args [i])
+            if i > 0:
+                inst_str = inst_str + ", "
+            inst_str = inst_str + type_printer.to_string ()
+        return inst_str
+
+class MonoGenericClassPrinter:
+    "Print a MonoGenericClass structure"
+
+    def __init__(self, val):
+        self.val = val
+
+    def to_string_inner(self):
+        gclass = self.val.dereference ()
+        container_str = str(gclass ["container_class"])
+        class_inst = gclass ["context"]["class_inst"]
+        class_inst_str = ""
+        if int(class_inst.cast (gdb.lookup_type ("guint64"))) != 0:
+            class_inst_str  = str(class_inst)
+        method_inst = gclass ["context"]["method_inst"]
+        method_inst_str = ""
+        if int(method_inst.cast (gdb.lookup_type ("guint64"))) != 0:
+            method_inst_str  = str(method_inst)
+        return "{}, [{}], [{}]>".format (container_str, class_inst_str, method_inst_str)
+
+    def to_string(self):
+        try:
+            return self.to_string_inner ()
+        except:
+            #print (sys.exc_info ()[0])
+            #print (sys.exc_info ()[1])
+            return str(self.val.cast (gdb.lookup_type ("gpointer")))
+
+class MonoTypePrinter:
+    "Print a MonoType structure"
+
+    def __init__(self, val):
+        self.val = val
+
+    def to_string_inner(self, csharp):
+        try:
+            t = self.val.referenced_value ()
+
+            kind = str (t ["type"]).replace ("MONO_TYPE_", "").lower ()
+            info = ""
+
+            if kind == "class":
+                p = MonoClassPrinter(t ["data"]["klass"])
+                info = p.to_string ()
+            elif kind == "genericinst":
+                info = str(t ["data"]["generic_class"])
+
+            if info != "":
+                return "{{{}, {}}}".format (kind, info)
+            else:
+                return "{{{}}}".format (kind)
+        except:
+            #print (sys.exc_info ()[0])
+            #print (sys.exc_info ()[1])
+            return str(self.val.cast (gdb.lookup_type ("gpointer")))
+
+    def to_string(self):
+        return self.to_string_inner (False)
+
+class MonoMethodRgctxPrinter:
+    "Print a MonoMethodRgctx structure"
+
+    def __init__(self, val):
+        self.val = val
+
+    def to_string(self):
+        rgctx = self.val.dereference ()
+        klass = rgctx ["class_vtable"].dereference () ["klass"]
+        klass_printer = MonoClassPrinter (klass)
+        inst = rgctx ["method_inst"].dereference ()
+        inst_len = inst ["type_argc"]
+        inst_args = inst ["type_argv"]
+        inst_str = ""
+        for i in range(0, inst_len):
+            # print (inst_args)
+            type_printer = MonoTypePrinter (inst_args [i])
+            if i > 0:
+                inst_str = inst_str + ", "
+            inst_str = inst_str + type_printer.to_string ()
+        return "MRGCTX[{}, [{}]]".format (klass_printer.to_string(), inst_str)
+
+class MonoVTablePrinter:
+    "Print a MonoVTable structure"
+
+    def __init__(self, val):
+        self.val = val
+
+    def to_string(self):
+        if int(self.val.cast (gdb.lookup_type ("guint64"))) == 0:
+            return "0x0"
+        vtable = self.val.dereference ()
+        klass = vtable ["klass"]
+        klass_printer = MonoClassPrinter (klass)
+
+        return "vtable({})".format (klass_printer.to_string ())
 
 def lookup_pretty_printer(val):
-    if str (val.type ()) == "object":
+    t = str (val.type)
+    if t == "object":
         return ObjectPrinter (val)
-    if str (val.type ()) == "string":
+    if t[0:5] == "class" and t[-1] == "&":
+        return ObjectPrinter (val)    
+    if t == "string":
         return StringPrinter (val)
+    if t == "MonoString *":
+        return StringPrinter (val)
+    if t == "MonoMethod *":
+        return MonoMethodPrinter (val)
+    if t == "MonoClass *":
+        return MonoClassPrinter (val)
+    if t == "MonoType *":
+        return MonoTypePrinter (val)
+    if t == "MonoGenericInst *":
+        return MonoGenericInstPrinter (val)
+    if t == "MonoGenericClass *":
+        return MonoGenericClassPrinter (val)
+    if t == "MonoMethodRuntimeGenericContext *":
+        return MonoMethodRgctxPrinter (val)
+    if t == "MonoVTable *":
+        return MonoVTablePrinter (val)
     return None
 
 def register_csharp_printers(obj):
@@ -134,54 +332,21 @@ def register_csharp_printers(obj):
 
     obj.pretty_printers.append (lookup_pretty_printer)
 
-register_csharp_printers (gdb.current_objfile())
-
-class MonoSupport(object):
-
-    def __init__(self):
-        self.s_size = 0
-
-    def run_hook(self):
-        if os.access ("xdb.s", os.F_OK):
-            os.remove ("xdb.s")
-        gdb.execute ("set environment MONO_XDEBUG 1")
-        
-    def stop_hook(self):
-        # Called when the program is stopped
-        # Need to recompile+reload the xdb.s file if needed
-        # FIXME: Need to detect half-written files created when the child is
-        # interrupted while executing the xdb.s writing code
-        # FIXME: Handle appdomain unload
-        if os.access ("xdb.s", os.F_OK):
-            new_size = os.stat ("xdb.s").st_size
-            if new_size > self.s_size:
-                sofile = "xdb.so"
-                gdb.execute ("shell as -o xdb.o xdb.s && ld -shared -o %s xdb.o" % sofile)
-                # FIXME: This prints messages which couldn't be turned off
-                gdb.execute ("add-symbol-file %s 0" % sofile)
-                self.s_size = new_size
-
-class RunHook (gdb.Command):
+# This command will flush the debugging info collected by the runtime
+class XdbCommand (gdb.Command):
     def __init__ (self):
-        super (RunHook, self).__init__ ("hook-run", gdb.COMMAND_NONE,
-                                        gdb.COMPLETE_COMMAND, pre_hook_of="run")
+        super (XdbCommand, self).__init__ ("xdb", gdb.COMMAND_NONE,
+                                           gdb.COMPLETE_COMMAND)
 
     def invoke(self, arg, from_tty):
-        mono_support.run_hook ()
+        gdb.execute ("call mono_xdebug_flush ()")
+
+register_csharp_printers (gdb.current_objfile())
 
-print "Mono support loaded."
+XdbCommand ()
 
-mono_support = MonoSupport ()
+gdb.execute ("set environment MONO_XDEBUG gdb")
 
-# This depends on the changes in gdb-python.diff to work
-#RunHook ()
+print ("Mono support loaded.")
 
-# Register our hooks
-# This currently cannot be done from python code
 
-exec_file = gdb.current_objfile ().filename
-# FIXME: Is there a way to detect symbolic links ?
-if os.stat (exec_file).st_size != os.lstat (exec_file).st_size:
-    exec_file = os.readlink (exec_file)
-exec_dir = os.path.dirname (exec_file)
-gdb.execute ("source %s/%s-gdbinit" % (exec_dir, os.path.basename (exec_file)))