<reponame>bealwang/MegEngine<gh_stars>1-10
import gdb

import re
import subprocess


_demangle_cache = {}


def demangle(name):
    if name not in _demangle_cache:
        _demangle_cache[name] = subprocess.run(['c++filt', "-t", name], stdout=subprocess.PIPE).stdout
    return _demangle_cache[name].decode('utf-8').strip()


def dynamic_cast(val):
    assert val.type.code == gdb.TYPE_CODE_REF
    val = val.cast(val.dynamic_type)
    return val


def eval_on_val(val, eval_str):
    if val.type.code == gdb.TYPE_CODE_REF:
        val = val.referenced_value()
    address = val.address
    eval_str = "(*({}){}){}".format(address.type, address, eval_str)
    return gdb.parse_and_eval(eval_str)


def is_subclass_of(subclass, baseclass):
    for field in subclass.fields():
        if field.is_base_class:
            if field.type == baseclass:
                return True
            elif is_subclass_of(field.type, baseclass):
                return True


def vector_size(vector):
    impl = vector["_M_impl"]
    return int(impl["_M_finish"] - impl["_M_start"])


def vector_item(vector, i):
    impl = vector["_M_impl"]
    return (impl["_M_start"] + i).dereference()


def shared_ptr_deref(ptr):
    return ptr["_M_ptr"].dereference()


def get_type_name(type_index):
    return gdb.lookup_global_symbol("mgb::imperative::debug::get_type_name(std::type_index const&)").value()(type_index).string()


mge_apply_transform_pattern = re.compile(r"^(.*)::apply_transform\(mgb::imperative::Operator const&, mgb::imperative::Span<mgb::imperative::ValueRef>\)$")
mge_op_fallback_pattern = re.compile(r"^(.*)::fallback\(mgb::imperative::Span<mgb::imperative::ValueRef>\) const$")


def is_mge_frame(frame):
    function = frame.function()
    if not (function and function.name):
        return False
    matcher = mge_apply_transform_pattern.match(function.name)
    if matcher:
        typename = matcher.group(1)
        return is_subclass_of(gdb.lookup_type(typename), gdb.lookup_type("mgb::imperative::Transform"))
    matcher = mge_op_fallback_pattern.match(function.name)
    if matcher:
        typename = matcher.group(1)
        return is_subclass_of(gdb.lookup_type(typename), gdb.lookup_type("mgb::imperative::Operator"))
    return False


def count_frame_level(frame):
    level = -1
    while frame:
        frame = frame.newer()
        level += 1
    return level


def print_mge_frame(frame):
    function = frame.function()
    op = eval_on_val(dynamic_cast(frame.read_var("op")), ".to_string().c_str()").string()
    inputs = str(frame.read_var("inputs"))
    matcher = mge_apply_transform_pattern.match(function.name)
    if matcher:
        name = matcher.group(1)
    else:
        name = mge_op_fallback_pattern.match(function.name).group(1)
    #TODO: span
    sal = frame.find_sal()
    filename = sal.symtab.filename
    line = sal.line
    print("#{} {} apply {} on {}, file: {}:{}".format(count_frame_level(frame), name, op, inputs, filename, line))


class MegengineBacktrace(gdb.Command):
    def __init__(self):
        super().__init__("mge-bt", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        frame = gdb.newest_frame()
        mge_stack = []
        while frame is not None:
            if is_mge_frame(frame):
                mge_stack.append(frame)
            frame = frame.older()
        for frame in reversed(mge_stack):
            print_mge_frame(frame)


class MegengineBreakApply(gdb.Command):
    def __init__(self):
        super().__init__("mge-brk-apply", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        gdb.Breakpoint("mgb::imperative::apply(mgb::imperative::Operator const&, mgb::imperative::Span<mgb::imperative::ValueRef>)")


class MegengineWatch(gdb.Command):
    def __init__(self):
        super().__init__("mge-watch", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        watch = gdb.lookup_global_symbol("mgb::imperative::debug::watch_value(mgb::imperative::ValueRef)").value()
        value = gdb.parse_and_eval(arg)
        watch(value)
        print("watching {}".format(str(value)))


class MegengineUp(gdb.Command):
    def __init__(self):
        super().__init__("mge-up", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        frame = gdb.selected_frame()
        if frame is None:
            print("Unable to find older dispatch frame")
            return
        frame = frame.older()
        while frame is not None and not is_mge_frame(frame):
            frame = frame.older()
        if frame is None:
            print("Unable to find older dispatch frame")
            return
        frame.select()
        print_mge_frame(frame)


class MegengineDown(gdb.Command):
    def __init__(self):
        super().__init__("mge-down", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        frame = gdb.selected_frame()
        if frame is None:
            print("Unable to find newer dispatch frame")
            return
        frame = frame.newer()
        while frame is not None and not is_mge_frame(frame):
            frame = frame.newer()
        if frame is None:
            print("Unable to find newer dispatch frame")
            return
        frame.select()
        print_mge_frame(frame)


class MegengineInfo(gdb.Command):
    def __init__(self):
        super().__init__("mge-info", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        if arg == "opr":
            registered_oprs = gdb.lookup_global_symbol("mgb::imperative::Operator::registered_types()").value()()
            size = vector_size(registered_oprs)
            for i in range(size):
                registered_opr = vector_item(registered_oprs, i)
                # name = eval_on_val(registered_opr, ".name()").string()
                name = get_type_name(registered_opr)
                print("{}: {}".format(i, demangle(name)))
        elif arg == "trf":
            dispatch_context = gdb.lookup_global_symbol("mgb::imperative::Transform::get_context()").value()()
            transformations = dispatch_context["transformations"]
            size = vector_size(transformations)
            for i in range(size):
                transformation = vector_item(transformations, i)
                # name = eval_on_val(transformation, ".get()->name().c_str()").string()
                name = shared_ptr_deref(transformation).dynamic_type.name
                # name = get_type_name(transformation)
                print("{}: {}".format(i, demangle(name)))
        else:
            print("Unsupported argument {}".format(arg))

    def complete(self, text, word):
        return ["opr", "trf"]



MegengineBacktrace()
MegengineBreakApply()
MegengineUp()
MegengineDown()
MegengineInfo()
MegengineWatch()

gdb.Breakpoint("mgb::imperative::debug::notify_event(char const*)")
import gdb

import re
import subprocess


_demangle_cache = {}


def demangle(name):
    if name not in _demangle_cache:
        _demangle_cache[name] = subprocess.run(['c++filt', "-t", name], stdout=subprocess.PIPE).stdout
    return _demangle_cache[name].decode('utf-8').strip()


def dynamic_cast(val):
    assert val.type.code == gdb.TYPE_CODE_REF
    val = val.cast(val.dynamic_type)
    return val


def eval_on_val(val, eval_str):
    if val.type.code == gdb.TYPE_CODE_REF:
        val = val.referenced_value()
    address = val.address
    eval_str = "(*({}){}){}".format(address.type, address, eval_str)
    return gdb.parse_and_eval(eval_str)


def is_subclass_of(subclass, baseclass):
    for field in subclass.fields():
        if field.is_base_class:
            if field.type == baseclass:
                return True
            elif is_subclass_of(field.type, baseclass):
                return True


def vector_size(vector):
    impl = vector["_M_impl"]
    return int(impl["_M_finish"] - impl["_M_start"])


def vector_item(vector, i):
    impl = vector["_M_impl"]
    return (impl["_M_start"] + i).dereference()


def shared_ptr_deref(ptr):
    return ptr["_M_ptr"].dereference()


def get_type_name(type_index):
    return gdb.lookup_global_symbol("mgb::imperative::debug::get_type_name(std::type_index const&)").value()(type_index).string()


mge_apply_transform_pattern = re.compile(r"^(.*)::apply_transform\(mgb::imperative::Operator const&, mgb::imperative::Span<mgb::imperative::ValueRef>\)$")
mge_op_fallback_pattern = re.compile(r"^(.*)::fallback\(mgb::imperative::Span<mgb::imperative::ValueRef>\) const$")


def is_mge_frame(frame):
    function = frame.function()
    if not (function and function.name):
        return False
    matcher = mge_apply_transform_pattern.match(function.name)
    if matcher:
        typename = matcher.group(1)
        return is_subclass_of(gdb.lookup_type(typename), gdb.lookup_type("mgb::imperative::Transform"))
    matcher = mge_op_fallback_pattern.match(function.name)
    if matcher:
        typename = matcher.group(1)
        return is_subclass_of(gdb.lookup_type(typename), gdb.lookup_type("mgb::imperative::Operator"))
    return False


def count_frame_level(frame):
    level = -1
    while frame:
        frame = frame.newer()
        level += 1
    return level


def print_mge_frame(frame):
    function = frame.function()
    op = eval_on_val(dynamic_cast(frame.read_var("op")), ".to_string().c_str()").string()
    inputs = str(frame.read_var("inputs"))
    matcher = mge_apply_transform_pattern.match(function.name)
    if matcher:
        name = matcher.group(1)
    else:
        name = mge_op_fallback_pattern.match(function.name).group(1)
    #TODO: span
    sal = frame.find_sal()
    filename = sal.symtab.filename
    line = sal.line
    print("#{} {} apply {} on {}, file: {}:{}".format(count_frame_level(frame), name, op, inputs, filename, line))


class MegengineBacktrace(gdb.Command):
    def __init__(self):
        super().__init__("mge-bt", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        frame = gdb.newest_frame()
        mge_stack = []
        while frame is not None:
            if is_mge_frame(frame):
                mge_stack.append(frame)
            frame = frame.older()
        for frame in reversed(mge_stack):
            print_mge_frame(frame)


class MegengineBreakApply(gdb.Command):
    def __init__(self):
        super().__init__("mge-brk-apply", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        gdb.Breakpoint("mgb::imperative::apply(mgb::imperative::Operator const&, mgb::imperative::Span<mgb::imperative::ValueRef>)")


class MegengineWatch(gdb.Command):
    def __init__(self):
        super().__init__("mge-watch", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        watch = gdb.lookup_global_symbol("mgb::imperative::debug::watch_value(mgb::imperative::ValueRef)").value()
        value = gdb.parse_and_eval(arg)
        watch(value)
        print("watching {}".format(str(value)))


class MegengineUp(gdb.Command):
    def __init__(self):
        super().__init__("mge-up", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        frame = gdb.selected_frame()
        if frame is None:
            print("Unable to find older dispatch frame")
            return
        frame = frame.older()
        while frame is not None and not is_mge_frame(frame):
            frame = frame.older()
        if frame is None:
            print("Unable to find older dispatch frame")
            return
        frame.select()
        print_mge_frame(frame)


class MegengineDown(gdb.Command):
    def __init__(self):
        super().__init__("mge-down", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        frame = gdb.selected_frame()
        if frame is None:
            print("Unable to find newer dispatch frame")
            return
        frame = frame.newer()
        while frame is not None and not is_mge_frame(frame):
            frame = frame.newer()
        if frame is None:
            print("Unable to find newer dispatch frame")
            return
        frame.select()
        print_mge_frame(frame)


class MegengineInfo(gdb.Command):
    def __init__(self):
        super().__init__("mge-info", gdb.COMMAND_USER)

    def invoke(self, arg, from_tty):
        if arg == "opr":
            registered_oprs = gdb.lookup_global_symbol("mgb::imperative::Operator::registered_types()").value()()
            size = vector_size(registered_oprs)
            for i in range(size):
                registered_opr = vector_item(registered_oprs, i)
                # name = eval_on_val(registered_opr, ".name()").string()
                name = get_type_name(registered_opr)
                print("{}: {}".format(i, demangle(name)))
        elif arg == "trf":
            dispatch_context = gdb.lookup_global_symbol("mgb::imperative::Transform::get_context()").value()()
            transformations = dispatch_context["transformations"]
            size = vector_size(transformations)
            for i in range(size):
                transformation = vector_item(transformations, i)
                # name = eval_on_val(transformation, ".get()->name().c_str()").string()
                name = shared_ptr_deref(transformation).dynamic_type.name
                # name = get_type_name(transformation)
                print("{}: {}".format(i, demangle(name)))
        else:
            print("Unsupported argument {}".format(arg))

    def complete(self, text, word):
        return ["opr", "trf"]



MegengineBacktrace()
MegengineBreakApply()
MegengineUp()
MegengineDown()
MegengineInfo()
MegengineWatch()

gdb.Breakpoint("mgb::imperative::debug::notify_event(char const*)")
#TODO: span # name = eval_on_val(registered_opr, ".name()").string() # name = eval_on_val(transformation, ".get()->name().c_str()").string() # name = get_type_name(transformation)