#
# Author: Zoltan Varga (vargaz@gmail.com)
# License: MIT/X11
#

#
# This is a mono support mode for gdb 7.0 and later
# Usage:
# - 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:
    "Print a C# string"

    def __init__(self, val):
        self.val = val

    def to_string(self):
        if int(self.val.cast (gdb.lookup_type ("guint64"))) == 0:
            return "null"

        obj = self.val.cast (gdb.lookup_type ("MonoString").pointer ()).dereference ()
        len = obj ['length']
        chars = obj ['chars']
        i = 0
        res = ['"']
        while i < len:
            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 ('"')
        return ''.join (res)

def stringify_class_name(ns, name):
    if ns == "System":
        if name == "Byte":
            return "byte"
        if name == "String":
            return "string"
    if ns == "":
        return name
    else:
        return "{}.{}".format (ns, name)

class ArrayPrinter:
    "Print a C# array"

    def __init__(self, val, class_ns, class_name):
        self.val = val
        self.class_ns = class_ns
        self.class_name = class_name

    def to_string(self):
        obj = self.val.cast (gdb.lookup_type ("MonoArray").pointer ()).dereference ()
        length = obj ['max_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):
        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__ ()
            pass

        def __iter__(self):
            return self

        def next(self):
            field = self.iter.next ()
            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.lookup_type ("guint64"))) == 0:
            return {}.__iter__ ()
        try:
            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_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.lookup_type ("guint64"))) == 0:
            return "null"
        try:
            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":
                return StringPrinter (self.val).to_string ()
            if class_name [-2:len(class_name)] == "[]":
                return ArrayPrinter (self.val,class_ns,class_name).to_string ()
            if class_ns != "":
                try:
                    gdb_type = gdb.lookup_type ("struct {}.{}".format (class_ns, class_name))
                except:
                    # Maybe there is no debug info for that type
                    return "{}.{}".format (class_ns, class_name)
                #return obj.cast (gdb_type)
                return "{}.{}".format (class_ns, class_name)
            return class_name
        except:
            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.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):
    t = str (val.type)
    if t == "object":
        return ObjectPrinter (val)
    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):
    "Register C# pretty-printers with objfile Obj."

    if obj == None:
        obj = gdb

    obj.pretty_printers.append (lookup_pretty_printer)

# This command will flush the debugging info collected by the runtime
class XdbCommand (gdb.Command):
    def __init__ (self):
        super (XdbCommand, self).__init__ ("xdb", gdb.COMMAND_NONE,
                                           gdb.COMPLETE_COMMAND)

    def invoke(self, arg, from_tty):
        gdb.execute ("call mono_xdebug_flush ()")

register_csharp_printers (gdb.current_objfile())

XdbCommand ()

gdb.execute ("set environment MONO_XDEBUG gdb")

print ("Mono support loaded.")