Source code for gdb

# Copyright (c) 2013-2015 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.

"""

XXX BPN - TO REVIEW: the doc must not match the GDB one because of the GDB doc
          licence restriction.

GDB compatible Python objects are provided in order customers can reuse
existing Python GDB scripts with the Wind River Debug Shell. The following doc
is extracted from the GDB documentation
(see https://sourceware.org/gdb/current/onlinedocs/gdb/)

gdb represents types from the inferior using the class gdb.Type.
The following type-related functions are available in the gdb module:
    gdb.lookup_type (name [, block])
This function looks up a type by name. @e name is the name of the type to look
up. It must be a string.
If @e block is given, then name is looked up in that scope. Otherwise, it is
searched for globally.
Ordinarily, this function will return an instance of gdb.Type. If the named
type cannot be found, it will throw an exception.
If the type is a structure or class type, or an enum type, the fields of that
type can be accessed using the Python dictionary syntax. For example, if
some_type is a gdb.Type instance holding a structure type, you can access its
foo field with:
    bar = some_type['foo']
bar will be a gdb.Field object; see below under the description of the
Type.fields method for a description of the gdb.Field class.
"""

import io
import os

import opensource.tcf.services.symbols as tcfsym

from windriver.utils import compat
from windriver.utils import logger as wrlogger
from windriver.tcf.utils import acpm

from .constants import *

from .Frame import Frame
from .GdbType import Type
from .GdbValue import Value
from .GdbField import Field
from .Inferior import Inferior
from .InferiorThread import InferiorThread
from .Progspace import Progspace

import types

pretty_printers = []
type_printers = []

shell = None


class error(RuntimeError):
    """This is the base class for most exceptions generated by gdb. It is
    derived from RuntimeError, for compatibility with earlier versions of gdb.
    If an error occurring in gdb does not fit into some more specific category,
    then the generated exception will have this type.
    """
    def __init__(self, value):
        super(error, self).__init__(value)


class GdbStream(io.TextIOWrapper):
    """A GDB shell standard output redirected to a file or a string.

    The *origin* is the original standard output of the shell to be written
    to a file (or a string) too.

    :param origin: The original output file of the shell.
    :type origin: |file|
    :param path: The file path to duplicate output to.
    :type path: |basestring|
    """
    def __init__(self, origin):
        self._origin = origin
        self._dual = io.BytesIO()
        self.fileno = self.origin.fileno
        self.isatty = self.origin.isatty

    def __repr__(self):
        return self.__class__.__name__ + '(' + repr(self._origin) + ')'

    def close(self):
        """Close both original channel and duplicate file."""
        if self._dual and not self._dual.closed:
            self._dual.close()

        if not self.origin.closed:
            self.origin.close()

    @property
    def closed(self):
        return self.origin.closed

    @property
    def encoding(self):
        return self.origin.encoding

    @property
    def errors(self):
        return self.origin.errors

    def getvalue(self):
        """Get stream content.

        Retrieve the entire contents of the *file* at any time before the
        StringIO object's close() method is called.
        """
        if hasattr(self._origin, 'getvalue'):
            value = self._origin.getvalue()
        else:
            value = self._dual.getvalue()
        if isinstance(value, (bytes, bytearray)):
            value = value.decode('utf-8')
        return value

    def flush(self):
        if self._dual:
            self._dual.flush()
        res = self.origin.flush()
        return res

    @property
    def mode(self):
        return self.origin.mode

    @property
    def name(self):
        return self.origin.name

    @property
    def newlines(self):
        return self.origin.newlines

    def __next__(self):
        return next(self.origin)

    @property
    def origin(self):
        return self._origin

    def read(self, *args, **kwargs):
        return self.origin.read(*args, **kwargs)

    def readinto(self, *args, **kwargs):
        return self.origin.readinto(*args, **kwargs)

    def readline(self, *args, **kwargs):
        return self.origin.readline(*args, **kwargs)

    def readlines(self, *args, **kwargs):
        return self.origin.readlines(*args, **kwargs)

    def seek(self, *args, **kwargs):
        self.origin.seek(*args, **kwargs)

    @property
    def softspace(self):
        return self.origin.softspace

    def tell(self):
        return self._dual.tell()

    def truncate(self, *args, **kwargs):
        return self.origin.truncate(*args, **kwargs)

    def write(self, msg, expected=''):  # Here
        try:
            if isinstance(msg, str):
                # Convert it to bytes
                self._dual.write(bytearray(msg, 'utf-8'))
            else:
                self._dual.write(msg)
            self._dual.flush()
        except:
            # Failing to write in the dual file should not prevent to write
            # in the main file.
            pass

        self.origin.write(msg)

    def writelines(self, lines):
        try:
            self._dual.writelines(lines)
        except:
            # Failing to write in the dual file should not prevent to write
            # in the main file.
            pass
        self.origin.writelines(lines)

    def xreadlines(self):
        return self.origin


def current_objfile():
    """Get current ObjFile object.

    This function returs the current ObjFile obect. If there is not current
    ObjFile, **None** is returned.

    :returns: A gdb.ObjFile object, or **None**.
    """
    return None


def current_progspace():
    """Get current program space object.

    This function returns the current Progspace object. If there is not current
    Progspace, **None** is returned.

    :returns: A gdb.Progspace object, or **None**.
    """
    from windriver.shells.wrdbg.utils import inferiors as wrdbginfs
    inferior = wrdbginfs.getCurrentInferior()
    if inferior and inferior.dbgContext():
        return Progspace(inferior.dbgContext())
    return None


[docs]def execute(command, from_tty=False, to_string=False,interpreter=False): """Execute a GDB command. Evaluate *command*, a |basestring|, as a GDB CLI command. .. If a GDB exception happens while command runs, it is translated as described in |Exception Handling|. Parameter *from_tty* specifies whether GDB ought to consider this command as having originated from the user invoking it interactively. It must be a |bool| value. If omitted, it defaults to **False**. By default, any output produced by command is sent to GDB's standard output. If the to_string parameter is **True**, then output will be collected by |gdb.execute| and returned as a |basestring|. The default is **False**, in which case the return value is **None**. .. If *to_string* is **True**, the GDB virtual terminal will be temporarily set to unlimited width and height, and its pagination will be disabled. .. seealso:: |Screen Size| .. seealso:: |gdb.STDOUT| """ from windriver.shells.wrdbg.utils import utils as wrdbgutils global shell global STDOUT global STDERR global STDLOG # Create the wrdbg shell it not already done if shell is None: # Do not create a new shell if there is already an interpreter if wrdbgutils.interp(): shell = wrdbgutils.interp() # Assign gdb Standard streams. STDOUT = GdbStream(shell.stdout) STDERR = GdbStream(shell.stderr) STDLOG = None else: import sys import windriver.shells.wrdbg.shell as wrdbg shell = wrdbg.WrDbg(stdout=GdbStream(sys.stdout), stderr=GdbStream(sys.stderr),interpreter=interpreter) # Assign gdb Standard streams. STDOUT = shell.stdout STDERR = shell.stderr STDLOG = None # Write out the prompt. STDOUT.write(shell.prompt) STDOUT.flush() # If from_tty is True, set the confirm mode off. confirm = None if not from_tty: # Result value must be 'on' or 'off' confirm = wrdbgutils.variables().get('confirm').value() wrdbgutils.variables().set('confirm', 'off') if not interpreter: STDOUT.write(command + '\n') STDOUT.flush() # Get the initial position of the shell output before sending the # command. Do this only if there is an expected value. initpos = STDOUT.tell() line = command.rstrip('\r\n') line = shell.precmd(line) stop = shell.onecmd(line) stop = shell.postcmd(stop, line) # If there is an expected value, compare the shell output with it cmdresult = '' try: STDOUT.flush() cmdresult = STDOUT.getvalue()[initpos:] except Exception as e: STDERR.write(str(e) + '\n') # Command has returned, we may print out the prompt. if not interpreter: STDOUT.write(shell.prompt) STDOUT.flush() if not from_tty and confirm is not None: # confirm value must be 'on' or 'off' wrdbgutils.variables().set('confirm', confirm) if to_string: return cmdresult
def flush(stream=None): """Flush the buffer of a GDB paginated stream so that the contents are displayed immediately. GDB will flush the contents of a stream automatically when it encounters a newline in the buffer. The optional stream determines the stream to flush. The default stream is GDB's standard output stream. Possible stream values are: - gdb.STDOUT -- GDB's standard output stream. - gdb.STDERR -- GDB's standard error stream. .. - gdb.STDLOG -- GDB's log stream (see Logging Output). .. :returns: The flushed lines. """ global STDOUT if stream: stream.flush() elif STDOUT: STDOUT.flush() def lookup_type(name, block=None): """Return a |Type| corresponding to the given name. This function looks up a type by its name. :param name: Name of the type to lookup. :type name: |basestring| :param block: (optional) Scope to lookup the type. If not provided, the type is lookup globally. :returns: |Type| :raises: An exception if the type is not found. """ if block is not None: msg = "lookup_type does not support block keyword yet." raise RuntimeError(msg) # The type name is not correctly computed in the case of C++, so we need # to remove extra keywords. if name.startswith("struct "): name = name[7:] elif name.startswith("union "): name = name[6:] elif name.startswith("enum "): name = name[5:] # Split the fullname to single names and search the last name in namespace scopeCtx, simple_name = search_inner_namespace(name) symbols = findSymbols(scopeCtx, simple_name) symbol = None for symbol in symbols: if symbol.getSymbolClass() != tcfsym.SymbolClass.type: continue pass if symbol is None and acpm.validate(): msg = 'Unable to find type name "{0}"'.format(name) raise RuntimeError(msg) # At that point, we are sure to find a correct type. Return a GDB type from gdb.GdbType import createType # @UnresolvedImport newType = createType(symId=symbol.getID()) return newType def parse_and_eval(expression): """Parse expression as an expression in the current language, evaluate it, and return the result as a gdb.Value. expression must be a string. This function can be useful when implementing a new command, as it provides a way to parse the command's argument as an expression. It is also useful simply to compute values, for example, it is the only way to get the value of a convenience variable as a gdb.Value. """ st = __name__ + '.parse_and_eval' wrlogger.traceIn(st, expression) assert isinstance(expression, compat.strings), \ "expression must be a string" from . import utils as gdbutils from windriver.tcf.debug import processes as dbgproc # Get the most inner context (frame, thread, process) debugContext = gdbutils.selected_context() gdbVal = gdb_eval(debugContext, expression) wrlogger.traceOut(st, 1, gdbVal) return gdbVal def default_visualizer(value): """Get pretty-printer for a value. :param value: The gdb.Value to consider. :returns: A pretty-printer object suitable for the *value*, or **None**. """ pprinter = None # Only check global printers for printer in pretty_printers: try: pprinter = printer(value) if pprinter is not None: break except: pass return pprinter def inferiors(): """Return a tuple containing all inferior objects.""" from windriver.shells.wrdbg.utils import inferiors as wrdbginfs gdbInferiors = [] inferiors = wrdbginfs.getInferiors() for inferior in inferiors: gdbInferior = Inferior(inferior.getNum(), inferior.dbgContext()) gdbInferiors.append(gdbInferior) return tuple(gdbInferiors) def objfiles(): """Return a tuple containing all objfiles objects.""" from windriver.shells.wrdbg.utils import inferiors as wrdbginfs gdbObjfiles = [] inferiors = wrdbginfs.getInferiors() for inferior in inferiors: gdbObjfiles += inferior.objfiles return tuple(gdbObjfiles) def progspaces(): """Return a tuple containing all program spaces objects.""" from windriver.shells.wrdbg.utils import inferiors as wrdbginfs gdbProgspaces = [] inferiors = wrdbginfs.getInferiors() for inferior in inferiors: # Inferior is valid only if it has a debug group (aka dbgContext()). group = inferior.dbgContext() if group: gdbProgspace = Progspace(group) gdbProgspaces.append(gdbProgspace) return tuple(gdbProgspaces) def selected_inferior(): """Return an object representing the current inferior.""" from windriver.shells.wrdbg.utils import inferiors as wrdbginfs inferior = wrdbginfs.getCurrentInferior() gdbInferior = Inferior(inferior.getNum(), inferior.dbgContext()) return gdbInferior def selected_thread(): """This function returns the thread object for the selected thread. If there is no selected thread, this will return None. """ from windriver.shells.wrdbg.utils import inferiors as wrdbginfs inferior = wrdbginfs.getCurrentInferior() if inferior.dbgContext() is None: return None thread = inferior.dbgContext().thread() if thread is None: return None gdbThread = InferiorThread(inferior.dbgContext(), thread.getID()) return gdbThread def selected_frame(): """Return the selected frame object.""" from windriver.shells.wrdbg.utils import inferiors as wrdbginfs inferior = wrdbginfs.getCurrentInferior() if inferior.dbgContext() is None: return None thread = inferior.dbgContext().thread() if thread is None: return None if not hasattr(thread, 'frames'): return None frame = thread.frames.selected() if frame is None: return None gdbFrame = Frame(frame) return gdbFrame # ------------------------- Internal functions ------------------------------ # def findSymbols(scopeCtx, name): """ :returns: A list of symbol contexts. """ from . import utils as gdbutils from windriver.tcf.apis import symbols as symapis dbgContext = gdbutils.selected_context() assert dbgContext is not None if scopeCtx is None: symbolIDs = symapis.findByName(dbgContext.getID(), name) else: try: symbolIDs = symapis.findInScope(dbgContext.getID(), 0, scopeCtx.getID(), name) except: symbolIDs = symapis.findByName(dbgContext.getID(), name) # Handle special case of the GNU printer for GCC 4.5.3 with a different # compiler (GCC 4.4.x). The template arguments contains spaces but the # symbol name in the debug info does not. Correct the name and search # again. if len(symbolIDs) == 0 and \ name.startswith("_Rb_tree_node< "): import re prog = re.compile(r"([\w:]+)<[ ]*([ \w,*:<>]+)>$") match = prog.search(name) newName = match.group(1) + "<%s>" templateArg = match.group(2).strip() match = prog.search(templateArg) newName = newName % (match.group(1) + "<%s> " % match.group(2).strip()) if scopeCtx is None: symbolIDs = symapis.findByName(dbgContext.getID(), newName) else: symbolIDs = symapis.findInScope(dbgContext.getID(), 0, scopeCtx.getID(), newName) symbols = [] acpmBurst = acpm.Burst() for symbolID in symbolIDs: try: symbols.append(symapis.get(symbolID)) except acpmBurst.dataMiss: acpmBurst.caught() return symbols def search_inner_namespace(fullname): import re # XXX bpn - we should not ignore the first '::', as it defines the global # namespace prog = re.compile(r"(?:::)?(\w+(?:<[ \w,*&:<>]+>)?)(?:::)?(.+)?") res = prog.match(fullname) namespace = res.group(1) name = res.group(2) if name is None: return (None, namespace) parentScope = None while True: try: symbols = findSymbols(parentScope, namespace) except Exception as firstexc: if parentScope: # Try without parent scope. If it fails, throw previous # exception. try: symbols = findSymbols(None, namespace) except: raise firstexc else: raise firstexc # The supplied name is a fullname. We have to seach the simple name in # in the current scope symbol = None for symbol in symbols: if symbol.getSymbolClass() == tcfsym.SymbolClass.namespace or \ (symbol.getSymbolClass() == tcfsym.SymbolClass.type and (symbol.getFlags() & (tcfsym.SYM_FLAG_STRUCT_TYPE | tcfsym.SYM_FLAG_CLASS_TYPE)) != 0): res = prog.match(name) namespace = res.group(1) name = res.group(2) if name is None: # Found the inner symbol and its parent return (symbol, namespace) parentScope = symbol break # continue with next namespace else: if acpm.validate(): # We end up in a cul-de-sac during the search. We should do a # clever search in the possible tree of namespaces, in the case # symbolIds is a list that contains more than one element. raise RuntimeError("Error finding inner namespace") if acpm.validate(): raise RuntimeError("Error finding inner namespace") return None, None def _assignment_expression(debugContext, expression): # TCF evaluator does not support assignation: use a dedicated # TCF command. expressions = expression.split('=') # We suppose that the left-hand side operand is a symbol name, and # the right hand-side is an expression. symbolName = expressions[0] gdbValue = gdb_eval(debugContext, expressions[1]) try: debugContext.expressions.assign(symbolName, gdbValue) except Exception as exc: msg = "unable to evaluate assignment expression "\ "'{0}': {1}".format(expression, str(exc)) raise error(msg) gdbVal = gdb_eval(debugContext, symbolName) return gdbVal def gdb_eval(context, expression): """Parse expression as an expression in the current language, evaluate it, and return the result as a gdb.Value. expression must be a string. This function can be useful when implementing a new command, as it provides a way to parse the command's argument as an expression. It is also useful simply to compute values, for example, it is the only way to get the value of a convenience variable as a gdb.Value. :param context: A debug context, or **None**. :type context: |DebugContext| or |DebugFrame| """ st = __name__ + '.gdb_eval' wrlogger.traceIn(st, context, expression) import windriver.tcf.debug as dbg assert isinstance(expression, compat.strings), \ "expression must be a string" assert context is None or \ isinstance(context, dbg.DebugContext) or \ isinstance(context, dbg.frames.DebugFrame), \ st + ": context must be a DebugContext or None" expression = expression.strip() # Parse roughly the expression to check for GDB variables names starting # with a $ character: # - $<x>, $, $$, $$<x> : <x> is an integer. Access value history. # - $<reg> : <reg> is an architecture register name or a known register # alias ($pc, $sp, $fp, $ps) # - $<name> : user convenience variable # - $_, $__ : value set by the x command if '$' in expression: msg = st + ": convenience variables are not supported yet." wrlogger.traceErr(st, 1, msg) raise NotImplementedError(msg) # At that point, any other $ references are either expression OS # variable or architecture registers that are known by the TCF # expression evaluator. gdbVal = None if context is None or not hasattr(context, 'expressions'): # Try to use the Python evaluator if none context is specified msg = "A context must be defined to create a variable" wrlogger.traceErr(st, 2, msg) raise NotImplementedError(msg) try: from gdb import GdbValue result = eval(expression, {}, {}) gdbVal = GdbValue.createValue(context, pythonValue=result) except Exception: pass wrlogger.traceOut(st, 3, gdbVal) return gdbVal if '=' in expression: # TCF evaluator does not support assignation: use a dedicated # TCF command. expressions = expression.split('=') if len(expressions) != 2: msg = st + ": support only one assignment operator" wrlogger.traceErr(st, 4, msg) raise NotImplementedError(msg) try: gdbVal = _assignment_expression(context, expression) except Exception as exc: wrlogger.traceErr(st, 5, str(exc)) raise else: try: gdbVal = context.expressions.evaluate(expression) except Exception as exc: wrlogger.traceErr(st, 3, str(exc)) msg = "unable to evaluate expression "\ "'{0}': {1}".format(expression, str(exc)) raise error(msg) wrlogger.traceOut(st, 7, gdbVal) return gdbVal