# The MIT License (MIT)
#
# Copyright (c) 2013 Jonathan Foote
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# Jonathan Foote
# jmfoote@loyola.edu

'''
A collection of Python objects that wrap and extend the GDB Python API based.

The objects in this file should generally not be instantiated directly--
they should be accessed via getTarget(), which selects the proper GDB wrapper
objects at runtime based on the architecture of the system executing the code.

Note that for many objects defined in this file, GDB is queried only when the
object is instantiated: if the state of the Inferior changes, any previously
created instances should be considered stale.
'''
try:
    import gdb
except ImportError as e:
    raise ImportError("This script must be run in GDB: ", str(e))

from lib.gdb_wrapper.arm import ArmTarget
from lib.gdb_wrapper.mips import MipsTarget
from lib.gdb_wrapper.qnx import QnxTarget
from lib.gdb_wrapper.asan import ASanTarget
from lib.gdb_wrapper.x86 import Target, x86Target

from lib.analyzers.x86 import Analyzer
from lib.analyzers.asan import ASanAnalyzer
from lib.analyzers.arm import ArmAnalyzer
from lib.analyzers.mips import MipsAnalyzer


class ArmASanTarget(ASanTarget, ArmTarget):
    '''
    A wrapper for an ARM Linux GDB Inferior enhanced with ASAN log output.
    '''
    pass


class QnxASanTarget(ASanTarget, QnxTarget):
    '''
    A wrapper for an ARM Linux (with QNX Neutrino kernel) GDB Inferior 
    enhanced with ASAN log output.
    '''
    pass


class ArmASanAnalyzer(ArmAnalyzer, ASanAnalyzer):
    '''
    A rule analyzer for ASAN log output from executing on ARM Linux. 
    '''
    pass


def getTarget(asan_log_file=None, bt_limit=0):
    ''' 
    Returns the current Target, which is a Python wrapper representing the 
    current state of the underlying Linux GDB Inferior object. This function
    selects a Target and Analyzer based on the architecture of the system at 
    runtime.
    '''

    # Get OS info.  TODO: verify this works on older versions of GDB (7.2)
    osabi = Target._re_gdb_osabi.search(str(gdb.execute("show osabi", False,
                                                        True))).group(1)
    # Try to use python API to determine architecture.
    arch = gdb.selected_frame().architecture().name()
    # Fallback parsing
    if arch == None:
        arch_str = str(gdb.execute("show architecture", False, True))
        # if we set the arch in gdbmultiarch it is "assumed"
        if "assumed" in arch_str:
            arch = arch_str
        else:
            arch = Target._re_gdb_arch.search(arch_str).group(1)

    # Instantiate a target based on the params and OS info
    # ASAN + i386 + *
    if asan_log_file and arch.startswith("i386"):
        target = ASanTarget(asan_log_file.read(), bt_limit)
        target.analyzer = ASanAnalyzer(target)
        return target
    # ASAN + ARM + QNX
    elif asan_log_file and arch.lower()[:3] == "arm" and osabi == "QNX Neutrino":
        target = QnxASanTarget(asan_log_file.read(), bt_limit)
        target.analyzer = ASanAnalyzer(target)
        return target
    # ASAN + ARM + *
    elif asan_log_file and arch.lower()[:3] == "arm":
        target = ArmASanTarget(asan_log_file.read(), bt_limit)
        target.analyzer = ArmASanAnalyzer(target)
        return target
    # * + ARM + QNX
    elif arch.lower()[:3] == "arm" and osabi == "QNX Neutrino":
        target = QnxTarget(bt_limit)
        target.analyzer = ArmAnalyzer(target)
        return target
    # * + i386 + *
    elif arch.startswith("i386"):
        target = x86Target(bt_limit)
        target.analyzer = Analyzer(target)
        return target
    # * + ARM + *
    elif arch.lower()[:3] == "arm":
        target = ArmTarget(bt_limit)
        target.analyzer = ArmAnalyzer(target)
        return target
    elif "mips" in arch.lower():
        target = MipsTarget(bt_limit)
        target.analyzer = MipsAnalyzer(target)
        return target
    else:
        raise NotImplementedError("no support for arch=%s and osabi=%s" % (arch, osabi))

    return Target(bt_limit)
