# 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