aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/tools/cython/Cython/Debugger
diff options
context:
space:
mode:
authorAnton Samokhvalov <pg83@yandex.ru>2022-02-10 16:45:15 +0300
committerDaniil Cherednik <dcherednik@yandex-team.ru>2022-02-10 16:45:15 +0300
commit72cb13b4aff9bc9cf22e49251bc8fd143f82538f (patch)
treeda2c34829458c7d4e74bdfbdf85dff449e9e7fb8 /contrib/tools/cython/Cython/Debugger
parent778e51ba091dc39e7b7fcab2b9cf4dbedfb6f2b5 (diff)
downloadydb-72cb13b4aff9bc9cf22e49251bc8fd143f82538f.tar.gz
Restoring authorship annotation for Anton Samokhvalov <pg83@yandex.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/tools/cython/Cython/Debugger')
-rw-r--r--contrib/tools/cython/Cython/Debugger/Cygdb.py284
-rw-r--r--contrib/tools/cython/Cython/Debugger/DebugWriter.py126
-rw-r--r--contrib/tools/cython/Cython/Debugger/Tests/TestLibCython.py508
-rw-r--r--contrib/tools/cython/Cython/Debugger/Tests/__init__.py2
-rw-r--r--contrib/tools/cython/Cython/Debugger/Tests/cfuncs.c16
-rw-r--r--contrib/tools/cython/Cython/Debugger/Tests/codefile94
-rw-r--r--contrib/tools/cython/Cython/Debugger/Tests/test_libcython_in_gdb.py970
-rw-r--r--contrib/tools/cython/Cython/Debugger/Tests/test_libpython_in_gdb.py208
-rw-r--r--contrib/tools/cython/Cython/Debugger/__init__.py2
-rw-r--r--contrib/tools/cython/Cython/Debugger/libcython.py2792
-rw-r--r--contrib/tools/cython/Cython/Debugger/libpython.py4508
11 files changed, 4755 insertions, 4755 deletions
diff --git a/contrib/tools/cython/Cython/Debugger/Cygdb.py b/contrib/tools/cython/Cython/Debugger/Cygdb.py
index 45f31ce6f7..a317020ab5 100644
--- a/contrib/tools/cython/Cython/Debugger/Cygdb.py
+++ b/contrib/tools/cython/Cython/Debugger/Cygdb.py
@@ -1,143 +1,143 @@
-#!/usr/bin/env python
-
-"""
-The Cython debugger
-
-The current directory should contain a directory named 'cython_debug', or a
-path to the cython project directory should be given (the parent directory of
-cython_debug).
-
-Additional gdb args can be provided only if a path to the project directory is
-given.
-"""
-
-import os
-import sys
-import glob
-import tempfile
-import textwrap
-import subprocess
-import optparse
-import logging
-
-logger = logging.getLogger(__name__)
-
-def make_command_file(path_to_debug_info, prefix_code='', no_import=False):
- if not no_import:
- pattern = os.path.join(path_to_debug_info,
- 'cython_debug',
- 'cython_debug_info_*')
- debug_files = glob.glob(pattern)
-
- if not debug_files:
- sys.exit('%s.\nNo debug files were found in %s. Aborting.' % (
- usage, os.path.abspath(path_to_debug_info)))
-
- fd, tempfilename = tempfile.mkstemp()
- f = os.fdopen(fd, 'w')
- try:
- f.write(prefix_code)
- f.write(textwrap.dedent('''\
- # This is a gdb command file
- # See https://sourceware.org/gdb/onlinedocs/gdb/Command-Files.html
-
- set breakpoint pending on
- set print pretty on
-
- python
- # Activate virtualenv, if we were launched from one
- import os
- virtualenv = os.getenv('VIRTUAL_ENV')
- if virtualenv:
- path_to_activate_this_py = os.path.join(virtualenv, 'bin', 'activate_this.py')
- print("gdb command file: Activating virtualenv: %s; path_to_activate_this_py: %s" % (
- virtualenv, path_to_activate_this_py))
- with open(path_to_activate_this_py) as f:
- exec(f.read(), dict(__file__=path_to_activate_this_py))
-
- from Cython.Debugger import libcython, libpython
- end
- '''))
-
- if no_import:
- # don't do this, this overrides file command in .gdbinit
- # f.write("file %s\n" % sys.executable)
- pass
- else:
- path = os.path.join(path_to_debug_info, "cython_debug", "interpreter")
- interpreter_file = open(path)
- try:
- interpreter = interpreter_file.read()
- finally:
- interpreter_file.close()
- f.write("file %s\n" % interpreter)
- f.write('\n'.join('cy import %s\n' % fn for fn in debug_files))
- f.write(textwrap.dedent('''\
- python
- import sys
- try:
- gdb.lookup_type('PyModuleObject')
- except RuntimeError:
- sys.stderr.write(
- 'Python was not compiled with debug symbols (or it was '
- 'stripped). Some functionality may not work (properly).\\n')
- end
-
- source .cygdbinit
- '''))
- finally:
- f.close()
-
- return tempfilename
-
-usage = "Usage: cygdb [options] [PATH [-- GDB_ARGUMENTS]]"
-
-def main(path_to_debug_info=None, gdb_argv=None, no_import=False):
- """
- Start the Cython debugger. This tells gdb to import the Cython and Python
- extensions (libcython.py and libpython.py) and it enables gdb's pending
- breakpoints.
-
- path_to_debug_info is the path to the Cython build directory
- gdb_argv is the list of options to gdb
- no_import tells cygdb whether it should import debug information
- """
- parser = optparse.OptionParser(usage=usage)
- parser.add_option("--gdb-executable",
- dest="gdb", default='gdb',
- help="gdb executable to use [default: gdb]")
- parser.add_option("--verbose", "-v",
- dest="verbosity", action="count", default=0,
- help="Verbose mode. Multiple -v options increase the verbosity")
-
- (options, args) = parser.parse_args()
- if path_to_debug_info is None:
- if len(args) > 1:
- path_to_debug_info = args[0]
- else:
- path_to_debug_info = os.curdir
-
- if gdb_argv is None:
- gdb_argv = args[1:]
-
- if path_to_debug_info == '--':
- no_import = True
-
- logging_level = logging.WARN
- if options.verbosity == 1:
- logging_level = logging.INFO
+#!/usr/bin/env python
+
+"""
+The Cython debugger
+
+The current directory should contain a directory named 'cython_debug', or a
+path to the cython project directory should be given (the parent directory of
+cython_debug).
+
+Additional gdb args can be provided only if a path to the project directory is
+given.
+"""
+
+import os
+import sys
+import glob
+import tempfile
+import textwrap
+import subprocess
+import optparse
+import logging
+
+logger = logging.getLogger(__name__)
+
+def make_command_file(path_to_debug_info, prefix_code='', no_import=False):
+ if not no_import:
+ pattern = os.path.join(path_to_debug_info,
+ 'cython_debug',
+ 'cython_debug_info_*')
+ debug_files = glob.glob(pattern)
+
+ if not debug_files:
+ sys.exit('%s.\nNo debug files were found in %s. Aborting.' % (
+ usage, os.path.abspath(path_to_debug_info)))
+
+ fd, tempfilename = tempfile.mkstemp()
+ f = os.fdopen(fd, 'w')
+ try:
+ f.write(prefix_code)
+ f.write(textwrap.dedent('''\
+ # This is a gdb command file
+ # See https://sourceware.org/gdb/onlinedocs/gdb/Command-Files.html
+
+ set breakpoint pending on
+ set print pretty on
+
+ python
+ # Activate virtualenv, if we were launched from one
+ import os
+ virtualenv = os.getenv('VIRTUAL_ENV')
+ if virtualenv:
+ path_to_activate_this_py = os.path.join(virtualenv, 'bin', 'activate_this.py')
+ print("gdb command file: Activating virtualenv: %s; path_to_activate_this_py: %s" % (
+ virtualenv, path_to_activate_this_py))
+ with open(path_to_activate_this_py) as f:
+ exec(f.read(), dict(__file__=path_to_activate_this_py))
+
+ from Cython.Debugger import libcython, libpython
+ end
+ '''))
+
+ if no_import:
+ # don't do this, this overrides file command in .gdbinit
+ # f.write("file %s\n" % sys.executable)
+ pass
+ else:
+ path = os.path.join(path_to_debug_info, "cython_debug", "interpreter")
+ interpreter_file = open(path)
+ try:
+ interpreter = interpreter_file.read()
+ finally:
+ interpreter_file.close()
+ f.write("file %s\n" % interpreter)
+ f.write('\n'.join('cy import %s\n' % fn for fn in debug_files))
+ f.write(textwrap.dedent('''\
+ python
+ import sys
+ try:
+ gdb.lookup_type('PyModuleObject')
+ except RuntimeError:
+ sys.stderr.write(
+ 'Python was not compiled with debug symbols (or it was '
+ 'stripped). Some functionality may not work (properly).\\n')
+ end
+
+ source .cygdbinit
+ '''))
+ finally:
+ f.close()
+
+ return tempfilename
+
+usage = "Usage: cygdb [options] [PATH [-- GDB_ARGUMENTS]]"
+
+def main(path_to_debug_info=None, gdb_argv=None, no_import=False):
+ """
+ Start the Cython debugger. This tells gdb to import the Cython and Python
+ extensions (libcython.py and libpython.py) and it enables gdb's pending
+ breakpoints.
+
+ path_to_debug_info is the path to the Cython build directory
+ gdb_argv is the list of options to gdb
+ no_import tells cygdb whether it should import debug information
+ """
+ parser = optparse.OptionParser(usage=usage)
+ parser.add_option("--gdb-executable",
+ dest="gdb", default='gdb',
+ help="gdb executable to use [default: gdb]")
+ parser.add_option("--verbose", "-v",
+ dest="verbosity", action="count", default=0,
+ help="Verbose mode. Multiple -v options increase the verbosity")
+
+ (options, args) = parser.parse_args()
+ if path_to_debug_info is None:
+ if len(args) > 1:
+ path_to_debug_info = args[0]
+ else:
+ path_to_debug_info = os.curdir
+
+ if gdb_argv is None:
+ gdb_argv = args[1:]
+
+ if path_to_debug_info == '--':
+ no_import = True
+
+ logging_level = logging.WARN
+ if options.verbosity == 1:
+ logging_level = logging.INFO
if options.verbosity >= 2:
- logging_level = logging.DEBUG
- logging.basicConfig(level=logging_level)
-
- logger.info("verbosity = %r", options.verbosity)
- logger.debug("options = %r; args = %r", options, args)
- logger.debug("Done parsing command-line options. path_to_debug_info = %r, gdb_argv = %r",
- path_to_debug_info, gdb_argv)
-
- tempfilename = make_command_file(path_to_debug_info, no_import=no_import)
- logger.info("Launching %s with command file: %s and gdb_argv: %s",
- options.gdb, tempfilename, gdb_argv)
+ logging_level = logging.DEBUG
+ logging.basicConfig(level=logging_level)
+
+ logger.info("verbosity = %r", options.verbosity)
+ logger.debug("options = %r; args = %r", options, args)
+ logger.debug("Done parsing command-line options. path_to_debug_info = %r, gdb_argv = %r",
+ path_to_debug_info, gdb_argv)
+
+ tempfilename = make_command_file(path_to_debug_info, no_import=no_import)
+ logger.info("Launching %s with command file: %s and gdb_argv: %s",
+ options.gdb, tempfilename, gdb_argv)
with open(tempfilename) as tempfile:
logger.debug('Command file (%s) contains: """\n%s"""', tempfilename, tempfile.read())
logger.info("Spawning %s...", options.gdb)
@@ -153,6 +153,6 @@ def main(path_to_debug_info=None, gdb_argv=None, no_import=False):
else:
break
logger.debug("Closing temp command file with fd: %s", tempfile.fileno())
- logger.debug("Removing temp command file: %s", tempfilename)
- os.remove(tempfilename)
- logger.debug("Removed temp command file: %s", tempfilename)
+ logger.debug("Removing temp command file: %s", tempfilename)
+ os.remove(tempfilename)
+ logger.debug("Removed temp command file: %s", tempfilename)
diff --git a/contrib/tools/cython/Cython/Debugger/DebugWriter.py b/contrib/tools/cython/Cython/Debugger/DebugWriter.py
index 876a3a2169..ebf99a2f64 100644
--- a/contrib/tools/cython/Cython/Debugger/DebugWriter.py
+++ b/contrib/tools/cython/Cython/Debugger/DebugWriter.py
@@ -1,72 +1,72 @@
from __future__ import absolute_import
-
-import os
-import sys
-import errno
-
-try:
- from lxml import etree
- have_lxml = True
-except ImportError:
- have_lxml = False
- try:
- from xml.etree import cElementTree as etree
- except ImportError:
- try:
- from xml.etree import ElementTree as etree
- except ImportError:
+
+import os
+import sys
+import errno
+
+try:
+ from lxml import etree
+ have_lxml = True
+except ImportError:
+ have_lxml = False
+ try:
+ from xml.etree import cElementTree as etree
+ except ImportError:
+ try:
+ from xml.etree import ElementTree as etree
+ except ImportError:
etree = None
-
+
from ..Compiler import Errors
-
-
-class CythonDebugWriter(object):
- """
- Class to output debugging information for cygdb
-
- It writes debug information to cython_debug/cython_debug_info_<modulename>
- in the build directory.
- """
-
- def __init__(self, output_dir):
- if etree is None:
- raise Errors.NoElementTreeInstalledException()
-
+
+
+class CythonDebugWriter(object):
+ """
+ Class to output debugging information for cygdb
+
+ It writes debug information to cython_debug/cython_debug_info_<modulename>
+ in the build directory.
+ """
+
+ def __init__(self, output_dir):
+ if etree is None:
+ raise Errors.NoElementTreeInstalledException()
+
self.output_dir = os.path.join(output_dir or os.curdir, 'cython_debug')
- self.tb = etree.TreeBuilder()
- # set by Cython.Compiler.ParseTreeTransforms.DebugTransform
- self.module_name = None
- self.start('cython_debug', attrs=dict(version='1.0'))
-
- def start(self, name, attrs=None):
- self.tb.start(name, attrs or {})
-
- def end(self, name):
- self.tb.end(name)
-
+ self.tb = etree.TreeBuilder()
+ # set by Cython.Compiler.ParseTreeTransforms.DebugTransform
+ self.module_name = None
+ self.start('cython_debug', attrs=dict(version='1.0'))
+
+ def start(self, name, attrs=None):
+ self.tb.start(name, attrs or {})
+
+ def end(self, name):
+ self.tb.end(name)
+
def add_entry(self, name, **attrs):
self.tb.start(name, attrs)
self.tb.end(name)
- def serialize(self):
- self.tb.end('Module')
- self.tb.end('cython_debug')
- xml_root_element = self.tb.close()
-
- try:
- os.makedirs(self.output_dir)
+ def serialize(self):
+ self.tb.end('Module')
+ self.tb.end('cython_debug')
+ xml_root_element = self.tb.close()
+
+ try:
+ os.makedirs(self.output_dir)
except OSError as e:
- if e.errno != errno.EEXIST:
- raise
-
- et = etree.ElementTree(xml_root_element)
- kw = {}
- if have_lxml:
- kw['pretty_print'] = True
-
- fn = "cython_debug_info_" + self.module_name
- et.write(os.path.join(self.output_dir, fn), encoding="UTF-8", **kw)
-
- interpreter_path = os.path.join(self.output_dir, 'interpreter')
- with open(interpreter_path, 'w') as f:
- f.write(sys.executable)
+ if e.errno != errno.EEXIST:
+ raise
+
+ et = etree.ElementTree(xml_root_element)
+ kw = {}
+ if have_lxml:
+ kw['pretty_print'] = True
+
+ fn = "cython_debug_info_" + self.module_name
+ et.write(os.path.join(self.output_dir, fn), encoding="UTF-8", **kw)
+
+ interpreter_path = os.path.join(self.output_dir, 'interpreter')
+ with open(interpreter_path, 'w') as f:
+ f.write(sys.executable)
diff --git a/contrib/tools/cython/Cython/Debugger/Tests/TestLibCython.py b/contrib/tools/cython/Cython/Debugger/Tests/TestLibCython.py
index 13560646ff..6a769cc2fc 100644
--- a/contrib/tools/cython/Cython/Debugger/Tests/TestLibCython.py
+++ b/contrib/tools/cython/Cython/Debugger/Tests/TestLibCython.py
@@ -1,274 +1,274 @@
-
-import os
-import re
-import sys
-import shutil
-import warnings
-import textwrap
-import unittest
-import tempfile
-import subprocess
-#import distutils.core
-#from distutils import sysconfig
-from distutils import ccompiler
-
-import runtests
-import Cython.Distutils.extension
+
+import os
+import re
+import sys
+import shutil
+import warnings
+import textwrap
+import unittest
+import tempfile
+import subprocess
+#import distutils.core
+#from distutils import sysconfig
+from distutils import ccompiler
+
+import runtests
+import Cython.Distutils.extension
import Cython.Distutils.old_build_ext as build_ext
-from Cython.Debugger import Cygdb as cygdb
-
-root = os.path.dirname(os.path.abspath(__file__))
-codefile = os.path.join(root, 'codefile')
-cfuncs_file = os.path.join(root, 'cfuncs.c')
-
-with open(codefile) as f:
- source_to_lineno = dict((line.strip(), i + 1) for i, line in enumerate(f))
-
-
-have_gdb = None
-def test_gdb():
- global have_gdb
- if have_gdb is not None:
- return have_gdb
-
+from Cython.Debugger import Cygdb as cygdb
+
+root = os.path.dirname(os.path.abspath(__file__))
+codefile = os.path.join(root, 'codefile')
+cfuncs_file = os.path.join(root, 'cfuncs.c')
+
+with open(codefile) as f:
+ source_to_lineno = dict((line.strip(), i + 1) for i, line in enumerate(f))
+
+
+have_gdb = None
+def test_gdb():
+ global have_gdb
+ if have_gdb is not None:
+ return have_gdb
+
have_gdb = False
- try:
+ try:
p = subprocess.Popen(['gdb', '-nx', '--version'], stdout=subprocess.PIPE)
- except OSError:
+ except OSError:
# gdb not found
gdb_version = None
- else:
+ else:
stdout, _ = p.communicate()
- # Based on Lib/test/test_gdb.py
+ # Based on Lib/test/test_gdb.py
regex = r"GNU gdb [^\d]*(\d+)\.(\d+)"
gdb_version = re.match(regex, stdout.decode('ascii', 'ignore'))
-
+
if gdb_version:
gdb_version_number = list(map(int, gdb_version.groups()))
- if gdb_version_number >= [7, 2]:
+ if gdb_version_number >= [7, 2]:
have_gdb = True
with tempfile.NamedTemporaryFile(mode='w+') as python_version_script:
- python_version_script.write(
- 'python import sys; print("%s %s" % sys.version_info[:2])')
- python_version_script.flush()
- p = subprocess.Popen(['gdb', '-batch', '-x', python_version_script.name],
- stdout=subprocess.PIPE)
+ python_version_script.write(
+ 'python import sys; print("%s %s" % sys.version_info[:2])')
+ python_version_script.flush()
+ p = subprocess.Popen(['gdb', '-batch', '-x', python_version_script.name],
+ stdout=subprocess.PIPE)
stdout, _ = p.communicate()
- try:
+ try:
internal_python_version = list(map(int, stdout.decode('ascii', 'ignore').split()))
if internal_python_version < [2, 6]:
have_gdb = False
- except ValueError:
- have_gdb = False
-
+ except ValueError:
+ have_gdb = False
+
if not have_gdb:
warnings.warn('Skipping gdb tests, need gdb >= 7.2 with Python >= 2.6')
-
- return have_gdb
-
-
-class DebuggerTestCase(unittest.TestCase):
-
- def setUp(self):
- """
- Run gdb and have cygdb import the debug information from the code
- defined in TestParseTreeTransforms's setUp method
- """
- if not test_gdb():
- return
-
- self.tempdir = tempfile.mkdtemp()
- self.destfile = os.path.join(self.tempdir, 'codefile.pyx')
- self.debug_dest = os.path.join(self.tempdir,
- 'cython_debug',
- 'cython_debug_info_codefile')
- self.cfuncs_destfile = os.path.join(self.tempdir, 'cfuncs')
-
- self.cwd = os.getcwd()
- try:
- os.chdir(self.tempdir)
-
- shutil.copy(codefile, self.destfile)
- shutil.copy(cfuncs_file, self.cfuncs_destfile + '.c')
+
+ return have_gdb
+
+
+class DebuggerTestCase(unittest.TestCase):
+
+ def setUp(self):
+ """
+ Run gdb and have cygdb import the debug information from the code
+ defined in TestParseTreeTransforms's setUp method
+ """
+ if not test_gdb():
+ return
+
+ self.tempdir = tempfile.mkdtemp()
+ self.destfile = os.path.join(self.tempdir, 'codefile.pyx')
+ self.debug_dest = os.path.join(self.tempdir,
+ 'cython_debug',
+ 'cython_debug_info_codefile')
+ self.cfuncs_destfile = os.path.join(self.tempdir, 'cfuncs')
+
+ self.cwd = os.getcwd()
+ try:
+ os.chdir(self.tempdir)
+
+ shutil.copy(codefile, self.destfile)
+ shutil.copy(cfuncs_file, self.cfuncs_destfile + '.c')
shutil.copy(cfuncs_file.replace('.c', '.h'),
self.cfuncs_destfile + '.h')
-
- compiler = ccompiler.new_compiler()
- compiler.compile(['cfuncs.c'], debug=True, extra_postargs=['-fPIC'])
-
- opts = dict(
- test_directory=self.tempdir,
- module='codefile',
- )
-
- optimization_disabler = build_ext.Optimization()
-
- cython_compile_testcase = runtests.CythonCompileTestCase(
- workdir=self.tempdir,
- # we clean up everything (not only compiled files)
- cleanup_workdir=False,
- tags=runtests.parse_tags(codefile),
- **opts
- )
-
-
- new_stderr = open(os.devnull, 'w')
-
- stderr = sys.stderr
- sys.stderr = new_stderr
-
- optimization_disabler.disable_optimization()
- try:
- cython_compile_testcase.run_cython(
- targetdir=self.tempdir,
- incdir=None,
- annotate=False,
- extra_compile_options={
- 'gdb_debug':True,
- 'output_dir':self.tempdir,
- },
- **opts
- )
-
- cython_compile_testcase.run_distutils(
- incdir=None,
- workdir=self.tempdir,
- extra_extension_args={'extra_objects':['cfuncs.o']},
- **opts
- )
- finally:
- optimization_disabler.restore_state()
- sys.stderr = stderr
- new_stderr.close()
-
- # ext = Cython.Distutils.extension.Extension(
- # 'codefile',
- # ['codefile.pyx'],
- # cython_gdb=True,
- # extra_objects=['cfuncs.o'])
- #
- # distutils.core.setup(
- # script_args=['build_ext', '--inplace'],
- # ext_modules=[ext],
- # cmdclass=dict(build_ext=Cython.Distutils.build_ext)
- # )
-
- except:
- os.chdir(self.cwd)
- raise
-
- def tearDown(self):
- if not test_gdb():
- return
- os.chdir(self.cwd)
- shutil.rmtree(self.tempdir)
-
-
-class GdbDebuggerTestCase(DebuggerTestCase):
-
- def setUp(self):
- if not test_gdb():
- return
-
- super(GdbDebuggerTestCase, self).setUp()
-
- prefix_code = textwrap.dedent('''\
- python
-
- import os
- import sys
- import traceback
-
- def excepthook(type, value, tb):
- traceback.print_exception(type, value, tb)
- sys.stderr.flush()
- sys.stdout.flush()
- os._exit(1)
-
- sys.excepthook = excepthook
-
- # Have tracebacks end up on sys.stderr (gdb replaces sys.stderr
- # with an object that calls gdb.write())
- sys.stderr = sys.__stderr__
-
- end
- ''')
-
- code = textwrap.dedent('''\
- python
-
- from Cython.Debugger.Tests import test_libcython_in_gdb
- test_libcython_in_gdb.main(version=%r)
-
- end
- ''' % (sys.version_info[:2],))
-
- self.gdb_command_file = cygdb.make_command_file(self.tempdir,
- prefix_code)
-
- with open(self.gdb_command_file, 'a') as f:
- f.write(code)
-
- args = ['gdb', '-batch', '-x', self.gdb_command_file, '-n', '--args',
- sys.executable, '-c', 'import codefile']
-
- paths = []
- path = os.environ.get('PYTHONPATH')
- if path:
- paths.append(path)
- paths.append(os.path.dirname(os.path.dirname(
- os.path.abspath(Cython.__file__))))
- env = dict(os.environ, PYTHONPATH=os.pathsep.join(paths))
-
- self.p = subprocess.Popen(
- args,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- env=env)
-
- def tearDown(self):
- if not test_gdb():
- return
-
- try:
- super(GdbDebuggerTestCase, self).tearDown()
- if self.p:
- try: self.p.stdout.close()
- except: pass
- try: self.p.stderr.close()
- except: pass
- self.p.wait()
- finally:
- os.remove(self.gdb_command_file)
-
-
-class TestAll(GdbDebuggerTestCase):
-
- def test_all(self):
- if not test_gdb():
- return
-
- out, err = self.p.communicate()
- out = out.decode('UTF-8')
- err = err.decode('UTF-8')
-
- exit_status = self.p.returncode
-
- if exit_status == 1:
- sys.stderr.write(out)
- sys.stderr.write(err)
- elif exit_status >= 2:
- border = u'*' * 30
- start = u'%s v INSIDE GDB v %s' % (border, border)
- stderr = u'%s v STDERR v %s' % (border, border)
- end = u'%s ^ INSIDE GDB ^ %s' % (border, border)
- errmsg = u'\n%s\n%s%s\n%s%s' % (start, out, stderr, err, end)
-
- sys.stderr.write(errmsg)
-
- # FIXME: re-enable this to make the test fail on internal failures
- #self.assertEqual(exit_status, 0)
-
-
-if __name__ == '__main__':
- unittest.main()
+
+ compiler = ccompiler.new_compiler()
+ compiler.compile(['cfuncs.c'], debug=True, extra_postargs=['-fPIC'])
+
+ opts = dict(
+ test_directory=self.tempdir,
+ module='codefile',
+ )
+
+ optimization_disabler = build_ext.Optimization()
+
+ cython_compile_testcase = runtests.CythonCompileTestCase(
+ workdir=self.tempdir,
+ # we clean up everything (not only compiled files)
+ cleanup_workdir=False,
+ tags=runtests.parse_tags(codefile),
+ **opts
+ )
+
+
+ new_stderr = open(os.devnull, 'w')
+
+ stderr = sys.stderr
+ sys.stderr = new_stderr
+
+ optimization_disabler.disable_optimization()
+ try:
+ cython_compile_testcase.run_cython(
+ targetdir=self.tempdir,
+ incdir=None,
+ annotate=False,
+ extra_compile_options={
+ 'gdb_debug':True,
+ 'output_dir':self.tempdir,
+ },
+ **opts
+ )
+
+ cython_compile_testcase.run_distutils(
+ incdir=None,
+ workdir=self.tempdir,
+ extra_extension_args={'extra_objects':['cfuncs.o']},
+ **opts
+ )
+ finally:
+ optimization_disabler.restore_state()
+ sys.stderr = stderr
+ new_stderr.close()
+
+ # ext = Cython.Distutils.extension.Extension(
+ # 'codefile',
+ # ['codefile.pyx'],
+ # cython_gdb=True,
+ # extra_objects=['cfuncs.o'])
+ #
+ # distutils.core.setup(
+ # script_args=['build_ext', '--inplace'],
+ # ext_modules=[ext],
+ # cmdclass=dict(build_ext=Cython.Distutils.build_ext)
+ # )
+
+ except:
+ os.chdir(self.cwd)
+ raise
+
+ def tearDown(self):
+ if not test_gdb():
+ return
+ os.chdir(self.cwd)
+ shutil.rmtree(self.tempdir)
+
+
+class GdbDebuggerTestCase(DebuggerTestCase):
+
+ def setUp(self):
+ if not test_gdb():
+ return
+
+ super(GdbDebuggerTestCase, self).setUp()
+
+ prefix_code = textwrap.dedent('''\
+ python
+
+ import os
+ import sys
+ import traceback
+
+ def excepthook(type, value, tb):
+ traceback.print_exception(type, value, tb)
+ sys.stderr.flush()
+ sys.stdout.flush()
+ os._exit(1)
+
+ sys.excepthook = excepthook
+
+ # Have tracebacks end up on sys.stderr (gdb replaces sys.stderr
+ # with an object that calls gdb.write())
+ sys.stderr = sys.__stderr__
+
+ end
+ ''')
+
+ code = textwrap.dedent('''\
+ python
+
+ from Cython.Debugger.Tests import test_libcython_in_gdb
+ test_libcython_in_gdb.main(version=%r)
+
+ end
+ ''' % (sys.version_info[:2],))
+
+ self.gdb_command_file = cygdb.make_command_file(self.tempdir,
+ prefix_code)
+
+ with open(self.gdb_command_file, 'a') as f:
+ f.write(code)
+
+ args = ['gdb', '-batch', '-x', self.gdb_command_file, '-n', '--args',
+ sys.executable, '-c', 'import codefile']
+
+ paths = []
+ path = os.environ.get('PYTHONPATH')
+ if path:
+ paths.append(path)
+ paths.append(os.path.dirname(os.path.dirname(
+ os.path.abspath(Cython.__file__))))
+ env = dict(os.environ, PYTHONPATH=os.pathsep.join(paths))
+
+ self.p = subprocess.Popen(
+ args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ env=env)
+
+ def tearDown(self):
+ if not test_gdb():
+ return
+
+ try:
+ super(GdbDebuggerTestCase, self).tearDown()
+ if self.p:
+ try: self.p.stdout.close()
+ except: pass
+ try: self.p.stderr.close()
+ except: pass
+ self.p.wait()
+ finally:
+ os.remove(self.gdb_command_file)
+
+
+class TestAll(GdbDebuggerTestCase):
+
+ def test_all(self):
+ if not test_gdb():
+ return
+
+ out, err = self.p.communicate()
+ out = out.decode('UTF-8')
+ err = err.decode('UTF-8')
+
+ exit_status = self.p.returncode
+
+ if exit_status == 1:
+ sys.stderr.write(out)
+ sys.stderr.write(err)
+ elif exit_status >= 2:
+ border = u'*' * 30
+ start = u'%s v INSIDE GDB v %s' % (border, border)
+ stderr = u'%s v STDERR v %s' % (border, border)
+ end = u'%s ^ INSIDE GDB ^ %s' % (border, border)
+ errmsg = u'\n%s\n%s%s\n%s%s' % (start, out, stderr, err, end)
+
+ sys.stderr.write(errmsg)
+
+ # FIXME: re-enable this to make the test fail on internal failures
+ #self.assertEqual(exit_status, 0)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/contrib/tools/cython/Cython/Debugger/Tests/__init__.py b/contrib/tools/cython/Cython/Debugger/Tests/__init__.py
index fa81adaff6..4a2889e8e1 100644
--- a/contrib/tools/cython/Cython/Debugger/Tests/__init__.py
+++ b/contrib/tools/cython/Cython/Debugger/Tests/__init__.py
@@ -1 +1 @@
-# empty file
+# empty file
diff --git a/contrib/tools/cython/Cython/Debugger/Tests/cfuncs.c b/contrib/tools/cython/Cython/Debugger/Tests/cfuncs.c
index ccb42050bf..263589e77f 100644
--- a/contrib/tools/cython/Cython/Debugger/Tests/cfuncs.c
+++ b/contrib/tools/cython/Cython/Debugger/Tests/cfuncs.c
@@ -1,8 +1,8 @@
-void
-some_c_function(void)
-{
- int a, b, c;
-
- a = 1;
- b = 2;
-}
+void
+some_c_function(void)
+{
+ int a, b, c;
+
+ a = 1;
+ b = 2;
+}
diff --git a/contrib/tools/cython/Cython/Debugger/Tests/codefile b/contrib/tools/cython/Cython/Debugger/Tests/codefile
index 6b4c6b6add..5c2b0c2957 100644
--- a/contrib/tools/cython/Cython/Debugger/Tests/codefile
+++ b/contrib/tools/cython/Cython/Debugger/Tests/codefile
@@ -1,50 +1,50 @@
-cdef extern from "stdio.h":
- int puts(char *s)
-
+cdef extern from "stdio.h":
+ int puts(char *s)
+
cdef extern from "cfuncs.h":
- void some_c_function()
-
-import os
-
-cdef int c_var = 12
-python_var = 13
-
-def spam(a=0):
- cdef:
- int b, c
-
- b = c = d = 0
-
- b = 1
- c = 2
- int(10)
- puts("spam")
- os.path.join("foo", "bar")
- some_c_function()
-
-cpdef eggs():
+ void some_c_function()
+
+import os
+
+cdef int c_var = 12
+python_var = 13
+
+def spam(a=0):
+ cdef:
+ int b, c
+
+ b = c = d = 0
+
+ b = 1
+ c = 2
+ int(10)
+ puts("spam")
+ os.path.join("foo", "bar")
+ some_c_function()
+
+cpdef eggs():
pass
-cdef ham():
- pass
-
-cdef class SomeClass(object):
- def spam(self):
- pass
-
-def outer():
- cdef object a = "an object"
- def inner():
- b = 2
- # access closed over variables
- print a, b
- return inner
-
-
-outer()()
-
-spam()
-print "bye!"
-
-def use_ham():
- ham()
+cdef ham():
+ pass
+
+cdef class SomeClass(object):
+ def spam(self):
+ pass
+
+def outer():
+ cdef object a = "an object"
+ def inner():
+ b = 2
+ # access closed over variables
+ print a, b
+ return inner
+
+
+outer()()
+
+spam()
+print "bye!"
+
+def use_ham():
+ ham()
diff --git a/contrib/tools/cython/Cython/Debugger/Tests/test_libcython_in_gdb.py b/contrib/tools/cython/Cython/Debugger/Tests/test_libcython_in_gdb.py
index bd7608d607..c4d4ace7c0 100644
--- a/contrib/tools/cython/Cython/Debugger/Tests/test_libcython_in_gdb.py
+++ b/contrib/tools/cython/Cython/Debugger/Tests/test_libcython_in_gdb.py
@@ -1,496 +1,496 @@
-"""
-Tests that run inside GDB.
-
-Note: debug information is already imported by the file generated by
-Cython.Debugger.Cygdb.make_command_file()
-"""
-
+"""
+Tests that run inside GDB.
+
+Note: debug information is already imported by the file generated by
+Cython.Debugger.Cygdb.make_command_file()
+"""
+
from __future__ import absolute_import
-import os
-import re
-import sys
-import trace
-import inspect
-import warnings
-import unittest
-import textwrap
-import tempfile
-import functools
-import traceback
-import itertools
+import os
+import re
+import sys
+import trace
+import inspect
+import warnings
+import unittest
+import textwrap
+import tempfile
+import functools
+import traceback
+import itertools
#from test import test_support
-
-import gdb
-
+
+import gdb
+
from .. import libcython
from .. import libpython
from . import TestLibCython as test_libcython
from ...Utils import add_metaclass
-
-# for some reason sys.argv is missing in gdb
-sys.argv = ['gdb']
-
-
-def print_on_call_decorator(func):
- @functools.wraps(func)
- def wrapper(self, *args, **kwargs):
- _debug(type(self).__name__, func.__name__)
-
- try:
- return func(self, *args, **kwargs)
+
+# for some reason sys.argv is missing in gdb
+sys.argv = ['gdb']
+
+
+def print_on_call_decorator(func):
+ @functools.wraps(func)
+ def wrapper(self, *args, **kwargs):
+ _debug(type(self).__name__, func.__name__)
+
+ try:
+ return func(self, *args, **kwargs)
except Exception:
_debug("An exception occurred:", traceback.format_exc())
- raise
-
- return wrapper
-
-class TraceMethodCallMeta(type):
-
- def __init__(self, name, bases, dict):
+ raise
+
+ return wrapper
+
+class TraceMethodCallMeta(type):
+
+ def __init__(self, name, bases, dict):
for func_name, func in dict.items():
- if inspect.isfunction(func):
- setattr(self, func_name, print_on_call_decorator(func))
-
-
+ if inspect.isfunction(func):
+ setattr(self, func_name, print_on_call_decorator(func))
+
+
@add_metaclass(TraceMethodCallMeta)
-class DebugTestCase(unittest.TestCase):
- """
- Base class for test cases. On teardown it kills the inferior and unsets
- all breakpoints.
- """
-
- def __init__(self, name):
- super(DebugTestCase, self).__init__(name)
- self.cy = libcython.cy
- self.module = libcython.cy.cython_namespace['codefile']
- self.spam_func, self.spam_meth = libcython.cy.functions_by_name['spam']
- self.ham_func = libcython.cy.functions_by_qualified_name[
- 'codefile.ham']
- self.eggs_func = libcython.cy.functions_by_qualified_name[
- 'codefile.eggs']
-
- def read_var(self, varname, cast_to=None):
- result = gdb.parse_and_eval('$cy_cvalue("%s")' % varname)
- if cast_to:
- result = cast_to(result)
-
- return result
-
- def local_info(self):
- return gdb.execute('info locals', to_string=True)
-
- def lineno_equals(self, source_line=None, lineno=None):
- if source_line is not None:
- lineno = test_libcython.source_to_lineno[source_line]
- frame = gdb.selected_frame()
- self.assertEqual(libcython.cython_info.lineno(frame), lineno)
-
- def break_and_run(self, source_line):
- break_lineno = test_libcython.source_to_lineno[source_line]
- gdb.execute('cy break codefile:%d' % break_lineno, to_string=True)
- gdb.execute('run', to_string=True)
-
- def tearDown(self):
- gdb.execute('delete breakpoints', to_string=True)
- try:
- gdb.execute('kill inferior 1', to_string=True)
- except RuntimeError:
- pass
-
- gdb.execute('set args -c "import codefile"')
-
-
-class TestDebugInformationClasses(DebugTestCase):
-
- def test_CythonModule(self):
- "test that debug information was parsed properly into data structures"
- self.assertEqual(self.module.name, 'codefile')
- global_vars = ('c_var', 'python_var', '__name__',
- '__builtins__', '__doc__', '__file__')
- assert set(global_vars).issubset(self.module.globals)
-
- def test_CythonVariable(self):
- module_globals = self.module.globals
- c_var = module_globals['c_var']
- python_var = module_globals['python_var']
- self.assertEqual(c_var.type, libcython.CObject)
- self.assertEqual(python_var.type, libcython.PythonObject)
- self.assertEqual(c_var.qualified_name, 'codefile.c_var')
-
- def test_CythonFunction(self):
- self.assertEqual(self.spam_func.qualified_name, 'codefile.spam')
- self.assertEqual(self.spam_meth.qualified_name,
- 'codefile.SomeClass.spam')
- self.assertEqual(self.spam_func.module, self.module)
-
- assert self.eggs_func.pf_cname, (self.eggs_func, self.eggs_func.pf_cname)
- assert not self.ham_func.pf_cname
- assert not self.spam_func.pf_cname
- assert not self.spam_meth.pf_cname
-
- self.assertEqual(self.spam_func.type, libcython.CObject)
- self.assertEqual(self.ham_func.type, libcython.CObject)
-
- self.assertEqual(self.spam_func.arguments, ['a'])
- self.assertEqual(self.spam_func.step_into_functions,
- set(['puts', 'some_c_function']))
-
- expected_lineno = test_libcython.source_to_lineno['def spam(a=0):']
- self.assertEqual(self.spam_func.lineno, expected_lineno)
- self.assertEqual(sorted(self.spam_func.locals), list('abcd'))
-
-
-class TestParameters(unittest.TestCase):
-
- def test_parameters(self):
- gdb.execute('set cy_colorize_code on')
- assert libcython.parameters.colorize_code
- gdb.execute('set cy_colorize_code off')
- assert not libcython.parameters.colorize_code
-
-
-class TestBreak(DebugTestCase):
-
- def test_break(self):
- breakpoint_amount = len(gdb.breakpoints() or ())
- gdb.execute('cy break codefile.spam')
-
- self.assertEqual(len(gdb.breakpoints()), breakpoint_amount + 1)
- bp = gdb.breakpoints()[-1]
- self.assertEqual(bp.type, gdb.BP_BREAKPOINT)
- assert self.spam_func.cname in bp.location
- assert bp.enabled
-
- def test_python_break(self):
- gdb.execute('cy break -p join')
- assert 'def join(' in gdb.execute('cy run', to_string=True)
-
- def test_break_lineno(self):
- beginline = 'import os'
- nextline = 'cdef int c_var = 12'
-
- self.break_and_run(beginline)
- self.lineno_equals(beginline)
- step_result = gdb.execute('cy step', to_string=True)
- self.lineno_equals(nextline)
- assert step_result.rstrip().endswith(nextline)
-
-
-class TestKilled(DebugTestCase):
-
- def test_abort(self):
- gdb.execute("set args -c 'import os; os.abort()'")
- output = gdb.execute('cy run', to_string=True)
- assert 'abort' in output.lower()
-
-
-class DebugStepperTestCase(DebugTestCase):
-
- def step(self, varnames_and_values, source_line=None, lineno=None):
- gdb.execute(self.command)
- for varname, value in varnames_and_values:
- self.assertEqual(self.read_var(varname), value, self.local_info())
-
- self.lineno_equals(source_line, lineno)
-
-
-class TestStep(DebugStepperTestCase):
- """
- Test stepping. Stepping happens in the code found in
- Cython/Debugger/Tests/codefile.
- """
-
- def test_cython_step(self):
- gdb.execute('cy break codefile.spam')
-
- gdb.execute('run', to_string=True)
- self.lineno_equals('def spam(a=0):')
-
- gdb.execute('cy step', to_string=True)
- self.lineno_equals('b = c = d = 0')
-
- self.command = 'cy step'
- self.step([('b', 0)], source_line='b = 1')
- self.step([('b', 1), ('c', 0)], source_line='c = 2')
- self.step([('c', 2)], source_line='int(10)')
- self.step([], source_line='puts("spam")')
-
- gdb.execute('cont', to_string=True)
- self.assertEqual(len(gdb.inferiors()), 1)
- self.assertEqual(gdb.inferiors()[0].pid, 0)
-
- def test_c_step(self):
- self.break_and_run('some_c_function()')
- gdb.execute('cy step', to_string=True)
- self.assertEqual(gdb.selected_frame().name(), 'some_c_function')
-
- def test_python_step(self):
- self.break_and_run('os.path.join("foo", "bar")')
-
- result = gdb.execute('cy step', to_string=True)
-
- curframe = gdb.selected_frame()
- self.assertEqual(curframe.name(), 'PyEval_EvalFrameEx')
-
- pyframe = libpython.Frame(curframe).get_pyop()
- # With Python 3 inferiors, pyframe.co_name will return a PyUnicodePtr,
- # be compatible
- frame_name = pyframe.co_name.proxyval(set())
- self.assertEqual(frame_name, 'join')
- assert re.match(r'\d+ def join\(', result), result
-
-
-class TestNext(DebugStepperTestCase):
-
- def test_cython_next(self):
- self.break_and_run('c = 2')
-
- lines = (
- 'int(10)',
- 'puts("spam")',
- 'os.path.join("foo", "bar")',
- 'some_c_function()',
- )
-
- for line in lines:
- gdb.execute('cy next')
- self.lineno_equals(line)
-
-
-class TestLocalsGlobals(DebugTestCase):
-
- def test_locals(self):
- self.break_and_run('int(10)')
-
- result = gdb.execute('cy locals', to_string=True)
- assert 'a = 0', repr(result)
- assert 'b = (int) 1', result
- assert 'c = (int) 2' in result, repr(result)
-
- def test_globals(self):
- self.break_and_run('int(10)')
-
- result = gdb.execute('cy globals', to_string=True)
- assert '__name__ ' in result, repr(result)
- assert '__doc__ ' in result, repr(result)
- assert 'os ' in result, repr(result)
- assert 'c_var ' in result, repr(result)
- assert 'python_var ' in result, repr(result)
-
-
-class TestBacktrace(DebugTestCase):
-
- def test_backtrace(self):
- libcython.parameters.colorize_code.value = False
-
- self.break_and_run('os.path.join("foo", "bar")')
-
- def match_backtrace_output(result):
- assert re.search(r'\#\d+ *0x.* in spam\(\) at .*codefile\.pyx:22',
- result), result
- assert 'os.path.join("foo", "bar")' in result, result
-
- result = gdb.execute('cy bt', to_string=True)
- match_backtrace_output(result)
-
- result = gdb.execute('cy bt -a', to_string=True)
- match_backtrace_output(result)
-
- # Apparently not everyone has main()
- # assert re.search(r'\#0 *0x.* in main\(\)', result), result
-
-
-class TestFunctions(DebugTestCase):
-
- def test_functions(self):
- self.break_and_run('c = 2')
- result = gdb.execute('print $cy_cname("b")', to_string=True)
- assert re.search('__pyx_.*b', result), result
-
- result = gdb.execute('print $cy_lineno()', to_string=True)
- supposed_lineno = test_libcython.source_to_lineno['c = 2']
- assert str(supposed_lineno) in result, (supposed_lineno, result)
-
- result = gdb.execute('print $cy_cvalue("b")', to_string=True)
- assert '= 1' in result
-
-
-class TestPrint(DebugTestCase):
-
- def test_print(self):
- self.break_and_run('c = 2')
- result = gdb.execute('cy print b', to_string=True)
- self.assertEqual('b = (int) 1\n', result)
-
-
-class TestUpDown(DebugTestCase):
-
- def test_updown(self):
- self.break_and_run('os.path.join("foo", "bar")')
- gdb.execute('cy step')
- self.assertRaises(RuntimeError, gdb.execute, 'cy down')
-
- result = gdb.execute('cy up', to_string=True)
- assert 'spam()' in result
- assert 'os.path.join("foo", "bar")' in result
-
-
-class TestExec(DebugTestCase):
-
- def setUp(self):
- super(TestExec, self).setUp()
- self.fd, self.tmpfilename = tempfile.mkstemp()
- self.tmpfile = os.fdopen(self.fd, 'r+')
-
- def tearDown(self):
- super(TestExec, self).tearDown()
-
- try:
- self.tmpfile.close()
- finally:
- os.remove(self.tmpfilename)
-
- def eval_command(self, command):
- gdb.execute('cy exec open(%r, "w").write(str(%s))' %
- (self.tmpfilename, command))
- return self.tmpfile.read().strip()
-
- def test_cython_exec(self):
- self.break_and_run('os.path.join("foo", "bar")')
-
- # test normal behaviour
- self.assertEqual("[0]", self.eval_command('[a]'))
-
- # test multiline code
- result = gdb.execute(textwrap.dedent('''\
- cy exec
- pass
-
- "nothing"
- end
- '''))
- result = self.tmpfile.read().rstrip()
- self.assertEqual('', result)
-
- def test_python_exec(self):
- self.break_and_run('os.path.join("foo", "bar")')
- gdb.execute('cy step')
-
- gdb.execute('cy exec some_random_var = 14')
- self.assertEqual('14', self.eval_command('some_random_var'))
-
-
-class CySet(DebugTestCase):
-
- def test_cyset(self):
- self.break_and_run('os.path.join("foo", "bar")')
-
- gdb.execute('cy set a = $cy_eval("{None: []}")')
- stringvalue = self.read_var("a", cast_to=str)
- self.assertEqual(stringvalue, "{None: []}")
-
-
-class TestCyEval(DebugTestCase):
- "Test the $cy_eval() gdb function."
-
- def test_cy_eval(self):
- # This function leaks a few objects in the GDB python process. This
- # is no biggie
- self.break_and_run('os.path.join("foo", "bar")')
-
- result = gdb.execute('print $cy_eval("None")', to_string=True)
- assert re.match(r'\$\d+ = None\n', result), result
-
- result = gdb.execute('print $cy_eval("[a]")', to_string=True)
- assert re.match(r'\$\d+ = \[0\]', result), result
-
-
-class TestClosure(DebugTestCase):
-
- def break_and_run_func(self, funcname):
- gdb.execute('cy break ' + funcname)
- gdb.execute('cy run')
-
- def test_inner(self):
- self.break_and_run_func('inner')
- self.assertEqual('', gdb.execute('cy locals', to_string=True))
-
- # Allow the Cython-generated code to initialize the scope variable
- gdb.execute('cy step')
-
- self.assertEqual(str(self.read_var('a')), "'an object'")
- print_result = gdb.execute('cy print a', to_string=True).strip()
- self.assertEqual(print_result, "a = 'an object'")
-
- def test_outer(self):
- self.break_and_run_func('outer')
- self.assertEqual('', gdb.execute('cy locals', to_string=True))
-
- # Initialize scope with 'a' uninitialized
- gdb.execute('cy step')
- self.assertEqual('', gdb.execute('cy locals', to_string=True))
-
- # Initialize 'a' to 1
- gdb.execute('cy step')
- print_result = gdb.execute('cy print a', to_string=True).strip()
- self.assertEqual(print_result, "a = 'an object'")
-
-
-_do_debug = os.environ.get('GDB_DEBUG')
-if _do_debug:
- _debug_file = open('/dev/tty', 'w')
-
-def _debug(*messages):
- if _do_debug:
- messages = itertools.chain([sys._getframe(1).f_code.co_name, ':'],
- messages)
- _debug_file.write(' '.join(str(msg) for msg in messages) + '\n')
-
-
-def run_unittest_in_module(modulename):
- try:
- gdb.lookup_type('PyModuleObject')
- except RuntimeError:
- msg = ("Unable to run tests, Python was not compiled with "
- "debugging information. Either compile python with "
- "-g or get a debug build (configure with --with-pydebug).")
- warnings.warn(msg)
- os._exit(1)
- else:
- m = __import__(modulename, fromlist=[''])
- tests = inspect.getmembers(m, inspect.isclass)
-
- # test_support.run_unittest(tests)
-
- test_loader = unittest.TestLoader()
- suite = unittest.TestSuite(
- [test_loader.loadTestsFromTestCase(cls) for name, cls in tests])
-
- result = unittest.TextTestRunner(verbosity=1).run(suite)
- return result.wasSuccessful()
-
-def runtests():
- """
- Run the libcython and libpython tests. Ensure that an appropriate status is
- returned to the parent test process.
- """
- from Cython.Debugger.Tests import test_libpython_in_gdb
-
- success_libcython = run_unittest_in_module(__name__)
- success_libpython = run_unittest_in_module(test_libpython_in_gdb.__name__)
-
- if not success_libcython or not success_libpython:
- sys.exit(2)
-
-def main(version, trace_code=False):
- global inferior_python_version
-
- inferior_python_version = version
-
- if trace_code:
- tracer = trace.Trace(count=False, trace=True, outfile=sys.stderr,
- ignoredirs=[sys.prefix, sys.exec_prefix])
- tracer.runfunc(runtests)
- else:
- runtests()
+class DebugTestCase(unittest.TestCase):
+ """
+ Base class for test cases. On teardown it kills the inferior and unsets
+ all breakpoints.
+ """
+
+ def __init__(self, name):
+ super(DebugTestCase, self).__init__(name)
+ self.cy = libcython.cy
+ self.module = libcython.cy.cython_namespace['codefile']
+ self.spam_func, self.spam_meth = libcython.cy.functions_by_name['spam']
+ self.ham_func = libcython.cy.functions_by_qualified_name[
+ 'codefile.ham']
+ self.eggs_func = libcython.cy.functions_by_qualified_name[
+ 'codefile.eggs']
+
+ def read_var(self, varname, cast_to=None):
+ result = gdb.parse_and_eval('$cy_cvalue("%s")' % varname)
+ if cast_to:
+ result = cast_to(result)
+
+ return result
+
+ def local_info(self):
+ return gdb.execute('info locals', to_string=True)
+
+ def lineno_equals(self, source_line=None, lineno=None):
+ if source_line is not None:
+ lineno = test_libcython.source_to_lineno[source_line]
+ frame = gdb.selected_frame()
+ self.assertEqual(libcython.cython_info.lineno(frame), lineno)
+
+ def break_and_run(self, source_line):
+ break_lineno = test_libcython.source_to_lineno[source_line]
+ gdb.execute('cy break codefile:%d' % break_lineno, to_string=True)
+ gdb.execute('run', to_string=True)
+
+ def tearDown(self):
+ gdb.execute('delete breakpoints', to_string=True)
+ try:
+ gdb.execute('kill inferior 1', to_string=True)
+ except RuntimeError:
+ pass
+
+ gdb.execute('set args -c "import codefile"')
+
+
+class TestDebugInformationClasses(DebugTestCase):
+
+ def test_CythonModule(self):
+ "test that debug information was parsed properly into data structures"
+ self.assertEqual(self.module.name, 'codefile')
+ global_vars = ('c_var', 'python_var', '__name__',
+ '__builtins__', '__doc__', '__file__')
+ assert set(global_vars).issubset(self.module.globals)
+
+ def test_CythonVariable(self):
+ module_globals = self.module.globals
+ c_var = module_globals['c_var']
+ python_var = module_globals['python_var']
+ self.assertEqual(c_var.type, libcython.CObject)
+ self.assertEqual(python_var.type, libcython.PythonObject)
+ self.assertEqual(c_var.qualified_name, 'codefile.c_var')
+
+ def test_CythonFunction(self):
+ self.assertEqual(self.spam_func.qualified_name, 'codefile.spam')
+ self.assertEqual(self.spam_meth.qualified_name,
+ 'codefile.SomeClass.spam')
+ self.assertEqual(self.spam_func.module, self.module)
+
+ assert self.eggs_func.pf_cname, (self.eggs_func, self.eggs_func.pf_cname)
+ assert not self.ham_func.pf_cname
+ assert not self.spam_func.pf_cname
+ assert not self.spam_meth.pf_cname
+
+ self.assertEqual(self.spam_func.type, libcython.CObject)
+ self.assertEqual(self.ham_func.type, libcython.CObject)
+
+ self.assertEqual(self.spam_func.arguments, ['a'])
+ self.assertEqual(self.spam_func.step_into_functions,
+ set(['puts', 'some_c_function']))
+
+ expected_lineno = test_libcython.source_to_lineno['def spam(a=0):']
+ self.assertEqual(self.spam_func.lineno, expected_lineno)
+ self.assertEqual(sorted(self.spam_func.locals), list('abcd'))
+
+
+class TestParameters(unittest.TestCase):
+
+ def test_parameters(self):
+ gdb.execute('set cy_colorize_code on')
+ assert libcython.parameters.colorize_code
+ gdb.execute('set cy_colorize_code off')
+ assert not libcython.parameters.colorize_code
+
+
+class TestBreak(DebugTestCase):
+
+ def test_break(self):
+ breakpoint_amount = len(gdb.breakpoints() or ())
+ gdb.execute('cy break codefile.spam')
+
+ self.assertEqual(len(gdb.breakpoints()), breakpoint_amount + 1)
+ bp = gdb.breakpoints()[-1]
+ self.assertEqual(bp.type, gdb.BP_BREAKPOINT)
+ assert self.spam_func.cname in bp.location
+ assert bp.enabled
+
+ def test_python_break(self):
+ gdb.execute('cy break -p join')
+ assert 'def join(' in gdb.execute('cy run', to_string=True)
+
+ def test_break_lineno(self):
+ beginline = 'import os'
+ nextline = 'cdef int c_var = 12'
+
+ self.break_and_run(beginline)
+ self.lineno_equals(beginline)
+ step_result = gdb.execute('cy step', to_string=True)
+ self.lineno_equals(nextline)
+ assert step_result.rstrip().endswith(nextline)
+
+
+class TestKilled(DebugTestCase):
+
+ def test_abort(self):
+ gdb.execute("set args -c 'import os; os.abort()'")
+ output = gdb.execute('cy run', to_string=True)
+ assert 'abort' in output.lower()
+
+
+class DebugStepperTestCase(DebugTestCase):
+
+ def step(self, varnames_and_values, source_line=None, lineno=None):
+ gdb.execute(self.command)
+ for varname, value in varnames_and_values:
+ self.assertEqual(self.read_var(varname), value, self.local_info())
+
+ self.lineno_equals(source_line, lineno)
+
+
+class TestStep(DebugStepperTestCase):
+ """
+ Test stepping. Stepping happens in the code found in
+ Cython/Debugger/Tests/codefile.
+ """
+
+ def test_cython_step(self):
+ gdb.execute('cy break codefile.spam')
+
+ gdb.execute('run', to_string=True)
+ self.lineno_equals('def spam(a=0):')
+
+ gdb.execute('cy step', to_string=True)
+ self.lineno_equals('b = c = d = 0')
+
+ self.command = 'cy step'
+ self.step([('b', 0)], source_line='b = 1')
+ self.step([('b', 1), ('c', 0)], source_line='c = 2')
+ self.step([('c', 2)], source_line='int(10)')
+ self.step([], source_line='puts("spam")')
+
+ gdb.execute('cont', to_string=True)
+ self.assertEqual(len(gdb.inferiors()), 1)
+ self.assertEqual(gdb.inferiors()[0].pid, 0)
+
+ def test_c_step(self):
+ self.break_and_run('some_c_function()')
+ gdb.execute('cy step', to_string=True)
+ self.assertEqual(gdb.selected_frame().name(), 'some_c_function')
+
+ def test_python_step(self):
+ self.break_and_run('os.path.join("foo", "bar")')
+
+ result = gdb.execute('cy step', to_string=True)
+
+ curframe = gdb.selected_frame()
+ self.assertEqual(curframe.name(), 'PyEval_EvalFrameEx')
+
+ pyframe = libpython.Frame(curframe).get_pyop()
+ # With Python 3 inferiors, pyframe.co_name will return a PyUnicodePtr,
+ # be compatible
+ frame_name = pyframe.co_name.proxyval(set())
+ self.assertEqual(frame_name, 'join')
+ assert re.match(r'\d+ def join\(', result), result
+
+
+class TestNext(DebugStepperTestCase):
+
+ def test_cython_next(self):
+ self.break_and_run('c = 2')
+
+ lines = (
+ 'int(10)',
+ 'puts("spam")',
+ 'os.path.join("foo", "bar")',
+ 'some_c_function()',
+ )
+
+ for line in lines:
+ gdb.execute('cy next')
+ self.lineno_equals(line)
+
+
+class TestLocalsGlobals(DebugTestCase):
+
+ def test_locals(self):
+ self.break_and_run('int(10)')
+
+ result = gdb.execute('cy locals', to_string=True)
+ assert 'a = 0', repr(result)
+ assert 'b = (int) 1', result
+ assert 'c = (int) 2' in result, repr(result)
+
+ def test_globals(self):
+ self.break_and_run('int(10)')
+
+ result = gdb.execute('cy globals', to_string=True)
+ assert '__name__ ' in result, repr(result)
+ assert '__doc__ ' in result, repr(result)
+ assert 'os ' in result, repr(result)
+ assert 'c_var ' in result, repr(result)
+ assert 'python_var ' in result, repr(result)
+
+
+class TestBacktrace(DebugTestCase):
+
+ def test_backtrace(self):
+ libcython.parameters.colorize_code.value = False
+
+ self.break_and_run('os.path.join("foo", "bar")')
+
+ def match_backtrace_output(result):
+ assert re.search(r'\#\d+ *0x.* in spam\(\) at .*codefile\.pyx:22',
+ result), result
+ assert 'os.path.join("foo", "bar")' in result, result
+
+ result = gdb.execute('cy bt', to_string=True)
+ match_backtrace_output(result)
+
+ result = gdb.execute('cy bt -a', to_string=True)
+ match_backtrace_output(result)
+
+ # Apparently not everyone has main()
+ # assert re.search(r'\#0 *0x.* in main\(\)', result), result
+
+
+class TestFunctions(DebugTestCase):
+
+ def test_functions(self):
+ self.break_and_run('c = 2')
+ result = gdb.execute('print $cy_cname("b")', to_string=True)
+ assert re.search('__pyx_.*b', result), result
+
+ result = gdb.execute('print $cy_lineno()', to_string=True)
+ supposed_lineno = test_libcython.source_to_lineno['c = 2']
+ assert str(supposed_lineno) in result, (supposed_lineno, result)
+
+ result = gdb.execute('print $cy_cvalue("b")', to_string=True)
+ assert '= 1' in result
+
+
+class TestPrint(DebugTestCase):
+
+ def test_print(self):
+ self.break_and_run('c = 2')
+ result = gdb.execute('cy print b', to_string=True)
+ self.assertEqual('b = (int) 1\n', result)
+
+
+class TestUpDown(DebugTestCase):
+
+ def test_updown(self):
+ self.break_and_run('os.path.join("foo", "bar")')
+ gdb.execute('cy step')
+ self.assertRaises(RuntimeError, gdb.execute, 'cy down')
+
+ result = gdb.execute('cy up', to_string=True)
+ assert 'spam()' in result
+ assert 'os.path.join("foo", "bar")' in result
+
+
+class TestExec(DebugTestCase):
+
+ def setUp(self):
+ super(TestExec, self).setUp()
+ self.fd, self.tmpfilename = tempfile.mkstemp()
+ self.tmpfile = os.fdopen(self.fd, 'r+')
+
+ def tearDown(self):
+ super(TestExec, self).tearDown()
+
+ try:
+ self.tmpfile.close()
+ finally:
+ os.remove(self.tmpfilename)
+
+ def eval_command(self, command):
+ gdb.execute('cy exec open(%r, "w").write(str(%s))' %
+ (self.tmpfilename, command))
+ return self.tmpfile.read().strip()
+
+ def test_cython_exec(self):
+ self.break_and_run('os.path.join("foo", "bar")')
+
+ # test normal behaviour
+ self.assertEqual("[0]", self.eval_command('[a]'))
+
+ # test multiline code
+ result = gdb.execute(textwrap.dedent('''\
+ cy exec
+ pass
+
+ "nothing"
+ end
+ '''))
+ result = self.tmpfile.read().rstrip()
+ self.assertEqual('', result)
+
+ def test_python_exec(self):
+ self.break_and_run('os.path.join("foo", "bar")')
+ gdb.execute('cy step')
+
+ gdb.execute('cy exec some_random_var = 14')
+ self.assertEqual('14', self.eval_command('some_random_var'))
+
+
+class CySet(DebugTestCase):
+
+ def test_cyset(self):
+ self.break_and_run('os.path.join("foo", "bar")')
+
+ gdb.execute('cy set a = $cy_eval("{None: []}")')
+ stringvalue = self.read_var("a", cast_to=str)
+ self.assertEqual(stringvalue, "{None: []}")
+
+
+class TestCyEval(DebugTestCase):
+ "Test the $cy_eval() gdb function."
+
+ def test_cy_eval(self):
+ # This function leaks a few objects in the GDB python process. This
+ # is no biggie
+ self.break_and_run('os.path.join("foo", "bar")')
+
+ result = gdb.execute('print $cy_eval("None")', to_string=True)
+ assert re.match(r'\$\d+ = None\n', result), result
+
+ result = gdb.execute('print $cy_eval("[a]")', to_string=True)
+ assert re.match(r'\$\d+ = \[0\]', result), result
+
+
+class TestClosure(DebugTestCase):
+
+ def break_and_run_func(self, funcname):
+ gdb.execute('cy break ' + funcname)
+ gdb.execute('cy run')
+
+ def test_inner(self):
+ self.break_and_run_func('inner')
+ self.assertEqual('', gdb.execute('cy locals', to_string=True))
+
+ # Allow the Cython-generated code to initialize the scope variable
+ gdb.execute('cy step')
+
+ self.assertEqual(str(self.read_var('a')), "'an object'")
+ print_result = gdb.execute('cy print a', to_string=True).strip()
+ self.assertEqual(print_result, "a = 'an object'")
+
+ def test_outer(self):
+ self.break_and_run_func('outer')
+ self.assertEqual('', gdb.execute('cy locals', to_string=True))
+
+ # Initialize scope with 'a' uninitialized
+ gdb.execute('cy step')
+ self.assertEqual('', gdb.execute('cy locals', to_string=True))
+
+ # Initialize 'a' to 1
+ gdb.execute('cy step')
+ print_result = gdb.execute('cy print a', to_string=True).strip()
+ self.assertEqual(print_result, "a = 'an object'")
+
+
+_do_debug = os.environ.get('GDB_DEBUG')
+if _do_debug:
+ _debug_file = open('/dev/tty', 'w')
+
+def _debug(*messages):
+ if _do_debug:
+ messages = itertools.chain([sys._getframe(1).f_code.co_name, ':'],
+ messages)
+ _debug_file.write(' '.join(str(msg) for msg in messages) + '\n')
+
+
+def run_unittest_in_module(modulename):
+ try:
+ gdb.lookup_type('PyModuleObject')
+ except RuntimeError:
+ msg = ("Unable to run tests, Python was not compiled with "
+ "debugging information. Either compile python with "
+ "-g or get a debug build (configure with --with-pydebug).")
+ warnings.warn(msg)
+ os._exit(1)
+ else:
+ m = __import__(modulename, fromlist=[''])
+ tests = inspect.getmembers(m, inspect.isclass)
+
+ # test_support.run_unittest(tests)
+
+ test_loader = unittest.TestLoader()
+ suite = unittest.TestSuite(
+ [test_loader.loadTestsFromTestCase(cls) for name, cls in tests])
+
+ result = unittest.TextTestRunner(verbosity=1).run(suite)
+ return result.wasSuccessful()
+
+def runtests():
+ """
+ Run the libcython and libpython tests. Ensure that an appropriate status is
+ returned to the parent test process.
+ """
+ from Cython.Debugger.Tests import test_libpython_in_gdb
+
+ success_libcython = run_unittest_in_module(__name__)
+ success_libpython = run_unittest_in_module(test_libpython_in_gdb.__name__)
+
+ if not success_libcython or not success_libpython:
+ sys.exit(2)
+
+def main(version, trace_code=False):
+ global inferior_python_version
+
+ inferior_python_version = version
+
+ if trace_code:
+ tracer = trace.Trace(count=False, trace=True, outfile=sys.stderr,
+ ignoredirs=[sys.prefix, sys.exec_prefix])
+ tracer.runfunc(runtests)
+ else:
+ runtests()
diff --git a/contrib/tools/cython/Cython/Debugger/Tests/test_libpython_in_gdb.py b/contrib/tools/cython/Cython/Debugger/Tests/test_libpython_in_gdb.py
index 6f34cee47b..8a2b83419b 100644
--- a/contrib/tools/cython/Cython/Debugger/Tests/test_libpython_in_gdb.py
+++ b/contrib/tools/cython/Cython/Debugger/Tests/test_libpython_in_gdb.py
@@ -1,115 +1,115 @@
-# -*- coding: UTF-8 -*-
-
-"""
-Test libpython.py. This is already partly tested by test_libcython_in_gdb and
-Lib/test/test_gdb.py in the Python source. These tests are run in gdb and
-called from test_libcython_in_gdb.main()
-"""
-
-import os
-import sys
-
-import gdb
-
-from Cython.Debugger import libcython
-from Cython.Debugger import libpython
-
+# -*- coding: UTF-8 -*-
+
+"""
+Test libpython.py. This is already partly tested by test_libcython_in_gdb and
+Lib/test/test_gdb.py in the Python source. These tests are run in gdb and
+called from test_libcython_in_gdb.main()
+"""
+
+import os
+import sys
+
+import gdb
+
+from Cython.Debugger import libcython
+from Cython.Debugger import libpython
+
from . import test_libcython_in_gdb
from .test_libcython_in_gdb import _debug, inferior_python_version
-
-
-class TestPrettyPrinters(test_libcython_in_gdb.DebugTestCase):
- """
- Test whether types of Python objects are correctly inferred and that
- the right libpython.PySomeTypeObjectPtr classes are instantiated.
-
- Also test whether values are appropriately formatted (don't be too
- laborious as Lib/test/test_gdb.py already covers this extensively).
-
- Don't take care of decreffing newly allocated objects as a new
- interpreter is started for every test anyway.
- """
-
- def setUp(self):
- super(TestPrettyPrinters, self).setUp()
- self.break_and_run('b = c = d = 0')
-
- def get_pyobject(self, code):
- value = gdb.parse_and_eval(code)
- assert libpython.pointervalue(value) != 0
- return value
-
- def pyobject_fromcode(self, code, gdbvar=None):
- if gdbvar is not None:
- d = {'varname':gdbvar, 'code':code}
- gdb.execute('set $%(varname)s = %(code)s' % d)
- code = '$' + gdbvar
-
- return libpython.PyObjectPtr.from_pyobject_ptr(self.get_pyobject(code))
-
- def get_repr(self, pyobject):
- return pyobject.get_truncated_repr(libpython.MAX_OUTPUT_LEN)
-
- def alloc_bytestring(self, string, gdbvar=None):
- if inferior_python_version < (3, 0):
- funcname = 'PyString_FromStringAndSize'
- else:
- funcname = 'PyBytes_FromStringAndSize'
-
+
+
+class TestPrettyPrinters(test_libcython_in_gdb.DebugTestCase):
+ """
+ Test whether types of Python objects are correctly inferred and that
+ the right libpython.PySomeTypeObjectPtr classes are instantiated.
+
+ Also test whether values are appropriately formatted (don't be too
+ laborious as Lib/test/test_gdb.py already covers this extensively).
+
+ Don't take care of decreffing newly allocated objects as a new
+ interpreter is started for every test anyway.
+ """
+
+ def setUp(self):
+ super(TestPrettyPrinters, self).setUp()
+ self.break_and_run('b = c = d = 0')
+
+ def get_pyobject(self, code):
+ value = gdb.parse_and_eval(code)
+ assert libpython.pointervalue(value) != 0
+ return value
+
+ def pyobject_fromcode(self, code, gdbvar=None):
+ if gdbvar is not None:
+ d = {'varname':gdbvar, 'code':code}
+ gdb.execute('set $%(varname)s = %(code)s' % d)
+ code = '$' + gdbvar
+
+ return libpython.PyObjectPtr.from_pyobject_ptr(self.get_pyobject(code))
+
+ def get_repr(self, pyobject):
+ return pyobject.get_truncated_repr(libpython.MAX_OUTPUT_LEN)
+
+ def alloc_bytestring(self, string, gdbvar=None):
+ if inferior_python_version < (3, 0):
+ funcname = 'PyString_FromStringAndSize'
+ else:
+ funcname = 'PyBytes_FromStringAndSize'
+
assert b'"' not in string
-
- # ensure double quotes
+
+ # ensure double quotes
code = '(PyObject *) %s("%s", %d)' % (funcname, string.decode('iso8859-1'), len(string))
- return self.pyobject_fromcode(code, gdbvar=gdbvar)
-
- def alloc_unicodestring(self, string, gdbvar=None):
- postfix = libpython.get_inferior_unicode_postfix()
+ return self.pyobject_fromcode(code, gdbvar=gdbvar)
+
+ def alloc_unicodestring(self, string, gdbvar=None):
+ postfix = libpython.get_inferior_unicode_postfix()
funcname = 'PyUnicode%s_DecodeUnicodeEscape' % (postfix,)
-
+
data = string.encode("unicode_escape").decode('iso8859-1')
- return self.pyobject_fromcode(
+ return self.pyobject_fromcode(
'(PyObject *) %s("%s", %d, "strict")' % (
funcname, data.replace('"', r'\"').replace('\\', r'\\'), len(data)),
- gdbvar=gdbvar)
-
- def test_bytestring(self):
+ gdbvar=gdbvar)
+
+ def test_bytestring(self):
bytestring = self.alloc_bytestring(b"spam")
-
- if inferior_python_version < (3, 0):
- bytestring_class = libpython.PyStringObjectPtr
+
+ if inferior_python_version < (3, 0):
+ bytestring_class = libpython.PyStringObjectPtr
expected = repr(b"spam")
- else:
- bytestring_class = libpython.PyBytesObjectPtr
- expected = "b'spam'"
-
- self.assertEqual(type(bytestring), bytestring_class)
- self.assertEqual(self.get_repr(bytestring), expected)
-
- def test_unicode(self):
- unicode_string = self.alloc_unicodestring(u"spam ἄλφα")
-
+ else:
+ bytestring_class = libpython.PyBytesObjectPtr
+ expected = "b'spam'"
+
+ self.assertEqual(type(bytestring), bytestring_class)
+ self.assertEqual(self.get_repr(bytestring), expected)
+
+ def test_unicode(self):
+ unicode_string = self.alloc_unicodestring(u"spam ἄλφα")
+
expected = u"'spam ἄλφα'"
- if inferior_python_version < (3, 0):
- expected = 'u' + expected
-
- self.assertEqual(type(unicode_string), libpython.PyUnicodeObjectPtr)
- self.assertEqual(self.get_repr(unicode_string), expected)
-
- def test_int(self):
- if inferior_python_version < (3, 0):
- intval = self.pyobject_fromcode('PyInt_FromLong(100)')
- self.assertEqual(type(intval), libpython.PyIntObjectPtr)
- self.assertEqual(self.get_repr(intval), '100')
-
- def test_long(self):
- longval = self.pyobject_fromcode('PyLong_FromLong(200)',
- gdbvar='longval')
- assert gdb.parse_and_eval('$longval->ob_type == &PyLong_Type')
-
- self.assertEqual(type(longval), libpython.PyLongObjectPtr)
- self.assertEqual(self.get_repr(longval), '200')
-
- def test_frame_type(self):
- frame = self.pyobject_fromcode('PyEval_GetFrame()')
-
- self.assertEqual(type(frame), libpython.PyFrameObjectPtr)
+ if inferior_python_version < (3, 0):
+ expected = 'u' + expected
+
+ self.assertEqual(type(unicode_string), libpython.PyUnicodeObjectPtr)
+ self.assertEqual(self.get_repr(unicode_string), expected)
+
+ def test_int(self):
+ if inferior_python_version < (3, 0):
+ intval = self.pyobject_fromcode('PyInt_FromLong(100)')
+ self.assertEqual(type(intval), libpython.PyIntObjectPtr)
+ self.assertEqual(self.get_repr(intval), '100')
+
+ def test_long(self):
+ longval = self.pyobject_fromcode('PyLong_FromLong(200)',
+ gdbvar='longval')
+ assert gdb.parse_and_eval('$longval->ob_type == &PyLong_Type')
+
+ self.assertEqual(type(longval), libpython.PyLongObjectPtr)
+ self.assertEqual(self.get_repr(longval), '200')
+
+ def test_frame_type(self):
+ frame = self.pyobject_fromcode('PyEval_GetFrame()')
+
+ self.assertEqual(type(frame), libpython.PyFrameObjectPtr)
diff --git a/contrib/tools/cython/Cython/Debugger/__init__.py b/contrib/tools/cython/Cython/Debugger/__init__.py
index fa81adaff6..4a2889e8e1 100644
--- a/contrib/tools/cython/Cython/Debugger/__init__.py
+++ b/contrib/tools/cython/Cython/Debugger/__init__.py
@@ -1 +1 @@
-# empty file
+# empty file
diff --git a/contrib/tools/cython/Cython/Debugger/libcython.py b/contrib/tools/cython/Cython/Debugger/libcython.py
index 23153789b6..2ddf43922e 100644
--- a/contrib/tools/cython/Cython/Debugger/libcython.py
+++ b/contrib/tools/cython/Cython/Debugger/libcython.py
@@ -1,23 +1,23 @@
-"""
-GDB extension that adds Cython support.
-"""
-
-from __future__ import print_function
-
+"""
+GDB extension that adds Cython support.
+"""
+
+from __future__ import print_function
+
try:
input = raw_input
except NameError:
pass
-import sys
-import textwrap
-import traceback
-import functools
-import itertools
-import collections
-
-import gdb
-
+import sys
+import textwrap
+import traceback
+import functools
+import itertools
+import collections
+
+import gdb
+
try: # python 2
UNICODE = unicode
BYTES = str
@@ -25,1410 +25,1410 @@ except NameError: # python 3
UNICODE = str
BYTES = bytes
-try:
- from lxml import etree
- have_lxml = True
-except ImportError:
- have_lxml = False
- try:
- # Python 2.5
- from xml.etree import cElementTree as etree
- except ImportError:
- try:
- # Python 2.5
- from xml.etree import ElementTree as etree
- except ImportError:
- try:
- # normal cElementTree install
- import cElementTree as etree
- except ImportError:
- # normal ElementTree install
- import elementtree.ElementTree as etree
-
-try:
- import pygments.lexers
- import pygments.formatters
-except ImportError:
- pygments = None
- sys.stderr.write("Install pygments for colorized source code.\n")
-
-if hasattr(gdb, 'string_to_argv'):
- from gdb import string_to_argv
-else:
- from shlex import split as string_to_argv
-
-from Cython.Debugger import libpython
-
-# C or Python type
-CObject = 'CObject'
-PythonObject = 'PythonObject'
-
-_data_types = dict(CObject=CObject, PythonObject=PythonObject)
-_filesystemencoding = sys.getfilesystemencoding() or 'UTF-8'
-
-
-# decorators
-
-def dont_suppress_errors(function):
- "*sigh*, readline"
- @functools.wraps(function)
- def wrapper(*args, **kwargs):
- try:
- return function(*args, **kwargs)
- except Exception:
- traceback.print_exc()
- raise
-
- return wrapper
-
-
-def default_selected_gdb_frame(err=True):
- def decorator(function):
- @functools.wraps(function)
- def wrapper(self, frame=None, *args, **kwargs):
- try:
- frame = frame or gdb.selected_frame()
- except RuntimeError:
- raise gdb.GdbError("No frame is currently selected.")
-
- if err and frame.name() is None:
- raise NoFunctionNameInFrameError()
-
- return function(self, frame, *args, **kwargs)
- return wrapper
- return decorator
-
-
-def require_cython_frame(function):
- @functools.wraps(function)
- @require_running_program
- def wrapper(self, *args, **kwargs):
- frame = kwargs.get('frame') or gdb.selected_frame()
- if not self.is_cython_function(frame):
- raise gdb.GdbError('Selected frame does not correspond with a '
- 'Cython function we know about.')
- return function(self, *args, **kwargs)
- return wrapper
-
-
-def dispatch_on_frame(c_command, python_command=None):
- def decorator(function):
- @functools.wraps(function)
- def wrapper(self, *args, **kwargs):
- is_cy = self.is_cython_function()
- is_py = self.is_python_function()
-
- if is_cy or (is_py and not python_command):
- function(self, *args, **kwargs)
- elif is_py:
- gdb.execute(python_command)
- elif self.is_relevant_function():
- gdb.execute(c_command)
- else:
- raise gdb.GdbError("Not a function cygdb knows about. "
- "Use the normal GDB commands instead.")
-
- return wrapper
- return decorator
-
-
-def require_running_program(function):
- @functools.wraps(function)
- def wrapper(*args, **kwargs):
- try:
- gdb.selected_frame()
- except RuntimeError:
- raise gdb.GdbError("No frame is currently selected.")
-
- return function(*args, **kwargs)
- return wrapper
-
-
-def gdb_function_value_to_unicode(function):
- @functools.wraps(function)
- def wrapper(self, string, *args, **kwargs):
- if isinstance(string, gdb.Value):
- string = string.string()
-
- return function(self, string, *args, **kwargs)
- return wrapper
-
-
-# Classes that represent the debug information
-# Don't rename the parameters of these classes, they come directly from the XML
-
-class CythonModule(object):
- def __init__(self, module_name, filename, c_filename):
- self.name = module_name
- self.filename = filename
- self.c_filename = c_filename
- self.globals = {}
- # {cython_lineno: min(c_linenos)}
- self.lineno_cy2c = {}
- # {c_lineno: cython_lineno}
- self.lineno_c2cy = {}
- self.functions = {}
-
-
-class CythonVariable(object):
-
- def __init__(self, name, cname, qualified_name, type, lineno):
- self.name = name
- self.cname = cname
- self.qualified_name = qualified_name
- self.type = type
- self.lineno = int(lineno)
-
-
-class CythonFunction(CythonVariable):
- def __init__(self,
- module,
- name,
- cname,
- pf_cname,
- qualified_name,
- lineno,
- type=CObject,
- is_initmodule_function="False"):
- super(CythonFunction, self).__init__(name,
- cname,
- qualified_name,
- type,
- lineno)
- self.module = module
- self.pf_cname = pf_cname
- self.is_initmodule_function = is_initmodule_function == "True"
- self.locals = {}
- self.arguments = []
- self.step_into_functions = set()
-
-
-# General purpose classes
-
-class CythonBase(object):
-
- @default_selected_gdb_frame(err=False)
- def is_cython_function(self, frame):
- return frame.name() in self.cy.functions_by_cname
-
- @default_selected_gdb_frame(err=False)
- def is_python_function(self, frame):
- """
- Tells if a frame is associated with a Python function.
- If we can't read the Python frame information, don't regard it as such.
- """
- if frame.name() == 'PyEval_EvalFrameEx':
- pyframe = libpython.Frame(frame).get_pyop()
- return pyframe and not pyframe.is_optimized_out()
- return False
-
- @default_selected_gdb_frame()
- def get_c_function_name(self, frame):
- return frame.name()
-
- @default_selected_gdb_frame()
- def get_c_lineno(self, frame):
- return frame.find_sal().line
-
- @default_selected_gdb_frame()
- def get_cython_function(self, frame):
- result = self.cy.functions_by_cname.get(frame.name())
- if result is None:
- raise NoCythonFunctionInFrameError()
-
- return result
-
- @default_selected_gdb_frame()
- def get_cython_lineno(self, frame):
- """
- Get the current Cython line number. Returns 0 if there is no
- correspondence between the C and Cython code.
- """
- cyfunc = self.get_cython_function(frame)
- return cyfunc.module.lineno_c2cy.get(self.get_c_lineno(frame), 0)
-
- @default_selected_gdb_frame()
- def get_source_desc(self, frame):
- filename = lineno = lexer = None
- if self.is_cython_function(frame):
- filename = self.get_cython_function(frame).module.filename
- lineno = self.get_cython_lineno(frame)
- if pygments:
- lexer = pygments.lexers.CythonLexer(stripall=False)
- elif self.is_python_function(frame):
- pyframeobject = libpython.Frame(frame).get_pyop()
-
- if not pyframeobject:
- raise gdb.GdbError(
- 'Unable to read information on python frame')
-
- filename = pyframeobject.filename()
- lineno = pyframeobject.current_line_num()
-
- if pygments:
- lexer = pygments.lexers.PythonLexer(stripall=False)
- else:
- symbol_and_line_obj = frame.find_sal()
- if not symbol_and_line_obj or not symbol_and_line_obj.symtab:
- filename = None
- lineno = 0
- else:
- filename = symbol_and_line_obj.symtab.fullname()
- lineno = symbol_and_line_obj.line
- if pygments:
- lexer = pygments.lexers.CLexer(stripall=False)
-
- return SourceFileDescriptor(filename, lexer), lineno
-
- @default_selected_gdb_frame()
- def get_source_line(self, frame):
- source_desc, lineno = self.get_source_desc()
- return source_desc.get_source(lineno)
-
- @default_selected_gdb_frame()
- def is_relevant_function(self, frame):
- """
- returns whether we care about a frame on the user-level when debugging
- Cython code
- """
- name = frame.name()
- older_frame = frame.older()
- if self.is_cython_function(frame) or self.is_python_function(frame):
- return True
- elif older_frame and self.is_cython_function(older_frame):
- # check for direct C function call from a Cython function
- cython_func = self.get_cython_function(older_frame)
- return name in cython_func.step_into_functions
-
- return False
-
- @default_selected_gdb_frame(err=False)
- def print_stackframe(self, frame, index, is_c=False):
- """
- Print a C, Cython or Python stack frame and the line of source code
- if available.
- """
- # do this to prevent the require_cython_frame decorator from
- # raising GdbError when calling self.cy.cy_cvalue.invoke()
- selected_frame = gdb.selected_frame()
- frame.select()
-
- try:
- source_desc, lineno = self.get_source_desc(frame)
- except NoFunctionNameInFrameError:
- print('#%-2d Unknown Frame (compile with -g)' % index)
- return
-
- if not is_c and self.is_python_function(frame):
- pyframe = libpython.Frame(frame).get_pyop()
- if pyframe is None or pyframe.is_optimized_out():
- # print this python function as a C function
- return self.print_stackframe(frame, index, is_c=True)
-
- func_name = pyframe.co_name
- func_cname = 'PyEval_EvalFrameEx'
- func_args = []
- elif self.is_cython_function(frame):
- cyfunc = self.get_cython_function(frame)
- f = lambda arg: self.cy.cy_cvalue.invoke(arg, frame=frame)
-
- func_name = cyfunc.name
- func_cname = cyfunc.cname
- func_args = [] # [(arg, f(arg)) for arg in cyfunc.arguments]
- else:
- source_desc, lineno = self.get_source_desc(frame)
- func_name = frame.name()
- func_cname = func_name
- func_args = []
-
- try:
- gdb_value = gdb.parse_and_eval(func_cname)
- except RuntimeError:
- func_address = 0
- else:
+try:
+ from lxml import etree
+ have_lxml = True
+except ImportError:
+ have_lxml = False
+ try:
+ # Python 2.5
+ from xml.etree import cElementTree as etree
+ except ImportError:
+ try:
+ # Python 2.5
+ from xml.etree import ElementTree as etree
+ except ImportError:
+ try:
+ # normal cElementTree install
+ import cElementTree as etree
+ except ImportError:
+ # normal ElementTree install
+ import elementtree.ElementTree as etree
+
+try:
+ import pygments.lexers
+ import pygments.formatters
+except ImportError:
+ pygments = None
+ sys.stderr.write("Install pygments for colorized source code.\n")
+
+if hasattr(gdb, 'string_to_argv'):
+ from gdb import string_to_argv
+else:
+ from shlex import split as string_to_argv
+
+from Cython.Debugger import libpython
+
+# C or Python type
+CObject = 'CObject'
+PythonObject = 'PythonObject'
+
+_data_types = dict(CObject=CObject, PythonObject=PythonObject)
+_filesystemencoding = sys.getfilesystemencoding() or 'UTF-8'
+
+
+# decorators
+
+def dont_suppress_errors(function):
+ "*sigh*, readline"
+ @functools.wraps(function)
+ def wrapper(*args, **kwargs):
+ try:
+ return function(*args, **kwargs)
+ except Exception:
+ traceback.print_exc()
+ raise
+
+ return wrapper
+
+
+def default_selected_gdb_frame(err=True):
+ def decorator(function):
+ @functools.wraps(function)
+ def wrapper(self, frame=None, *args, **kwargs):
+ try:
+ frame = frame or gdb.selected_frame()
+ except RuntimeError:
+ raise gdb.GdbError("No frame is currently selected.")
+
+ if err and frame.name() is None:
+ raise NoFunctionNameInFrameError()
+
+ return function(self, frame, *args, **kwargs)
+ return wrapper
+ return decorator
+
+
+def require_cython_frame(function):
+ @functools.wraps(function)
+ @require_running_program
+ def wrapper(self, *args, **kwargs):
+ frame = kwargs.get('frame') or gdb.selected_frame()
+ if not self.is_cython_function(frame):
+ raise gdb.GdbError('Selected frame does not correspond with a '
+ 'Cython function we know about.')
+ return function(self, *args, **kwargs)
+ return wrapper
+
+
+def dispatch_on_frame(c_command, python_command=None):
+ def decorator(function):
+ @functools.wraps(function)
+ def wrapper(self, *args, **kwargs):
+ is_cy = self.is_cython_function()
+ is_py = self.is_python_function()
+
+ if is_cy or (is_py and not python_command):
+ function(self, *args, **kwargs)
+ elif is_py:
+ gdb.execute(python_command)
+ elif self.is_relevant_function():
+ gdb.execute(c_command)
+ else:
+ raise gdb.GdbError("Not a function cygdb knows about. "
+ "Use the normal GDB commands instead.")
+
+ return wrapper
+ return decorator
+
+
+def require_running_program(function):
+ @functools.wraps(function)
+ def wrapper(*args, **kwargs):
+ try:
+ gdb.selected_frame()
+ except RuntimeError:
+ raise gdb.GdbError("No frame is currently selected.")
+
+ return function(*args, **kwargs)
+ return wrapper
+
+
+def gdb_function_value_to_unicode(function):
+ @functools.wraps(function)
+ def wrapper(self, string, *args, **kwargs):
+ if isinstance(string, gdb.Value):
+ string = string.string()
+
+ return function(self, string, *args, **kwargs)
+ return wrapper
+
+
+# Classes that represent the debug information
+# Don't rename the parameters of these classes, they come directly from the XML
+
+class CythonModule(object):
+ def __init__(self, module_name, filename, c_filename):
+ self.name = module_name
+ self.filename = filename
+ self.c_filename = c_filename
+ self.globals = {}
+ # {cython_lineno: min(c_linenos)}
+ self.lineno_cy2c = {}
+ # {c_lineno: cython_lineno}
+ self.lineno_c2cy = {}
+ self.functions = {}
+
+
+class CythonVariable(object):
+
+ def __init__(self, name, cname, qualified_name, type, lineno):
+ self.name = name
+ self.cname = cname
+ self.qualified_name = qualified_name
+ self.type = type
+ self.lineno = int(lineno)
+
+
+class CythonFunction(CythonVariable):
+ def __init__(self,
+ module,
+ name,
+ cname,
+ pf_cname,
+ qualified_name,
+ lineno,
+ type=CObject,
+ is_initmodule_function="False"):
+ super(CythonFunction, self).__init__(name,
+ cname,
+ qualified_name,
+ type,
+ lineno)
+ self.module = module
+ self.pf_cname = pf_cname
+ self.is_initmodule_function = is_initmodule_function == "True"
+ self.locals = {}
+ self.arguments = []
+ self.step_into_functions = set()
+
+
+# General purpose classes
+
+class CythonBase(object):
+
+ @default_selected_gdb_frame(err=False)
+ def is_cython_function(self, frame):
+ return frame.name() in self.cy.functions_by_cname
+
+ @default_selected_gdb_frame(err=False)
+ def is_python_function(self, frame):
+ """
+ Tells if a frame is associated with a Python function.
+ If we can't read the Python frame information, don't regard it as such.
+ """
+ if frame.name() == 'PyEval_EvalFrameEx':
+ pyframe = libpython.Frame(frame).get_pyop()
+ return pyframe and not pyframe.is_optimized_out()
+ return False
+
+ @default_selected_gdb_frame()
+ def get_c_function_name(self, frame):
+ return frame.name()
+
+ @default_selected_gdb_frame()
+ def get_c_lineno(self, frame):
+ return frame.find_sal().line
+
+ @default_selected_gdb_frame()
+ def get_cython_function(self, frame):
+ result = self.cy.functions_by_cname.get(frame.name())
+ if result is None:
+ raise NoCythonFunctionInFrameError()
+
+ return result
+
+ @default_selected_gdb_frame()
+ def get_cython_lineno(self, frame):
+ """
+ Get the current Cython line number. Returns 0 if there is no
+ correspondence between the C and Cython code.
+ """
+ cyfunc = self.get_cython_function(frame)
+ return cyfunc.module.lineno_c2cy.get(self.get_c_lineno(frame), 0)
+
+ @default_selected_gdb_frame()
+ def get_source_desc(self, frame):
+ filename = lineno = lexer = None
+ if self.is_cython_function(frame):
+ filename = self.get_cython_function(frame).module.filename
+ lineno = self.get_cython_lineno(frame)
+ if pygments:
+ lexer = pygments.lexers.CythonLexer(stripall=False)
+ elif self.is_python_function(frame):
+ pyframeobject = libpython.Frame(frame).get_pyop()
+
+ if not pyframeobject:
+ raise gdb.GdbError(
+ 'Unable to read information on python frame')
+
+ filename = pyframeobject.filename()
+ lineno = pyframeobject.current_line_num()
+
+ if pygments:
+ lexer = pygments.lexers.PythonLexer(stripall=False)
+ else:
+ symbol_and_line_obj = frame.find_sal()
+ if not symbol_and_line_obj or not symbol_and_line_obj.symtab:
+ filename = None
+ lineno = 0
+ else:
+ filename = symbol_and_line_obj.symtab.fullname()
+ lineno = symbol_and_line_obj.line
+ if pygments:
+ lexer = pygments.lexers.CLexer(stripall=False)
+
+ return SourceFileDescriptor(filename, lexer), lineno
+
+ @default_selected_gdb_frame()
+ def get_source_line(self, frame):
+ source_desc, lineno = self.get_source_desc()
+ return source_desc.get_source(lineno)
+
+ @default_selected_gdb_frame()
+ def is_relevant_function(self, frame):
+ """
+ returns whether we care about a frame on the user-level when debugging
+ Cython code
+ """
+ name = frame.name()
+ older_frame = frame.older()
+ if self.is_cython_function(frame) or self.is_python_function(frame):
+ return True
+ elif older_frame and self.is_cython_function(older_frame):
+ # check for direct C function call from a Cython function
+ cython_func = self.get_cython_function(older_frame)
+ return name in cython_func.step_into_functions
+
+ return False
+
+ @default_selected_gdb_frame(err=False)
+ def print_stackframe(self, frame, index, is_c=False):
+ """
+ Print a C, Cython or Python stack frame and the line of source code
+ if available.
+ """
+ # do this to prevent the require_cython_frame decorator from
+ # raising GdbError when calling self.cy.cy_cvalue.invoke()
+ selected_frame = gdb.selected_frame()
+ frame.select()
+
+ try:
+ source_desc, lineno = self.get_source_desc(frame)
+ except NoFunctionNameInFrameError:
+ print('#%-2d Unknown Frame (compile with -g)' % index)
+ return
+
+ if not is_c and self.is_python_function(frame):
+ pyframe = libpython.Frame(frame).get_pyop()
+ if pyframe is None or pyframe.is_optimized_out():
+ # print this python function as a C function
+ return self.print_stackframe(frame, index, is_c=True)
+
+ func_name = pyframe.co_name
+ func_cname = 'PyEval_EvalFrameEx'
+ func_args = []
+ elif self.is_cython_function(frame):
+ cyfunc = self.get_cython_function(frame)
+ f = lambda arg: self.cy.cy_cvalue.invoke(arg, frame=frame)
+
+ func_name = cyfunc.name
+ func_cname = cyfunc.cname
+ func_args = [] # [(arg, f(arg)) for arg in cyfunc.arguments]
+ else:
+ source_desc, lineno = self.get_source_desc(frame)
+ func_name = frame.name()
+ func_cname = func_name
+ func_args = []
+
+ try:
+ gdb_value = gdb.parse_and_eval(func_cname)
+ except RuntimeError:
+ func_address = 0
+ else:
func_address = gdb_value.address
if not isinstance(func_address, int):
# Seriously? Why is the address not an int?
if not isinstance(func_address, (str, bytes)):
func_address = str(func_address)
func_address = int(func_address.split()[0], 0)
-
- a = ', '.join('%s=%s' % (name, val) for name, val in func_args)
- sys.stdout.write('#%-2d 0x%016x in %s(%s)' % (index, func_address, func_name, a))
-
- if source_desc.filename is not None:
- sys.stdout.write(' at %s:%s' % (source_desc.filename, lineno))
-
- sys.stdout.write('\n')
-
- try:
- sys.stdout.write(' ' + source_desc.get_source(lineno))
- except gdb.GdbError:
- pass
-
- selected_frame.select()
-
- def get_remote_cython_globals_dict(self):
- m = gdb.parse_and_eval('__pyx_m')
-
- try:
- PyModuleObject = gdb.lookup_type('PyModuleObject')
- except RuntimeError:
- raise gdb.GdbError(textwrap.dedent("""\
- Unable to lookup type PyModuleObject, did you compile python
- with debugging support (-g)?"""))
-
- m = m.cast(PyModuleObject.pointer())
- return m['md_dict']
-
-
- def get_cython_globals_dict(self):
- """
- Get the Cython globals dict where the remote names are turned into
- local strings.
- """
- remote_dict = self.get_remote_cython_globals_dict()
- pyobject_dict = libpython.PyObjectPtr.from_pyobject_ptr(remote_dict)
-
- result = {}
- seen = set()
+
+ a = ', '.join('%s=%s' % (name, val) for name, val in func_args)
+ sys.stdout.write('#%-2d 0x%016x in %s(%s)' % (index, func_address, func_name, a))
+
+ if source_desc.filename is not None:
+ sys.stdout.write(' at %s:%s' % (source_desc.filename, lineno))
+
+ sys.stdout.write('\n')
+
+ try:
+ sys.stdout.write(' ' + source_desc.get_source(lineno))
+ except gdb.GdbError:
+ pass
+
+ selected_frame.select()
+
+ def get_remote_cython_globals_dict(self):
+ m = gdb.parse_and_eval('__pyx_m')
+
+ try:
+ PyModuleObject = gdb.lookup_type('PyModuleObject')
+ except RuntimeError:
+ raise gdb.GdbError(textwrap.dedent("""\
+ Unable to lookup type PyModuleObject, did you compile python
+ with debugging support (-g)?"""))
+
+ m = m.cast(PyModuleObject.pointer())
+ return m['md_dict']
+
+
+ def get_cython_globals_dict(self):
+ """
+ Get the Cython globals dict where the remote names are turned into
+ local strings.
+ """
+ remote_dict = self.get_remote_cython_globals_dict()
+ pyobject_dict = libpython.PyObjectPtr.from_pyobject_ptr(remote_dict)
+
+ result = {}
+ seen = set()
for k, v in pyobject_dict.items():
- result[k.proxyval(seen)] = v
-
- return result
-
- def print_gdb_value(self, name, value, max_name_length=None, prefix=''):
- if libpython.pretty_printer_lookup(value):
- typename = ''
- else:
- typename = '(%s) ' % (value.type,)
-
- if max_name_length is None:
- print('%s%s = %s%s' % (prefix, name, typename, value))
- else:
- print('%s%-*s = %s%s' % (prefix, max_name_length, name, typename, value))
-
- def is_initialized(self, cython_func, local_name):
- cyvar = cython_func.locals[local_name]
- cur_lineno = self.get_cython_lineno()
-
- if '->' in cyvar.cname:
- # Closed over free variable
- if cur_lineno > cython_func.lineno:
- if cyvar.type == PythonObject:
+ result[k.proxyval(seen)] = v
+
+ return result
+
+ def print_gdb_value(self, name, value, max_name_length=None, prefix=''):
+ if libpython.pretty_printer_lookup(value):
+ typename = ''
+ else:
+ typename = '(%s) ' % (value.type,)
+
+ if max_name_length is None:
+ print('%s%s = %s%s' % (prefix, name, typename, value))
+ else:
+ print('%s%-*s = %s%s' % (prefix, max_name_length, name, typename, value))
+
+ def is_initialized(self, cython_func, local_name):
+ cyvar = cython_func.locals[local_name]
+ cur_lineno = self.get_cython_lineno()
+
+ if '->' in cyvar.cname:
+ # Closed over free variable
+ if cur_lineno > cython_func.lineno:
+ if cyvar.type == PythonObject:
return int(gdb.parse_and_eval(cyvar.cname))
- return True
- return False
-
- return cur_lineno > cyvar.lineno
-
-
-class SourceFileDescriptor(object):
- def __init__(self, filename, lexer, formatter=None):
- self.filename = filename
- self.lexer = lexer
- self.formatter = formatter
-
- def valid(self):
- return self.filename is not None
-
- def lex(self, code):
- if pygments and self.lexer and parameters.colorize_code:
- bg = parameters.terminal_background.value
- if self.formatter is None:
- formatter = pygments.formatters.TerminalFormatter(bg=bg)
- else:
- formatter = self.formatter
-
- return pygments.highlight(code, self.lexer, formatter)
-
- return code
-
- def _get_source(self, start, stop, lex_source, mark_line, lex_entire):
- with open(self.filename) as f:
- # to provide "correct" colouring, the entire code needs to be
- # lexed. However, this makes a lot of things terribly slow, so
- # we decide not to. Besides, it's unlikely to matter.
-
- if lex_source and lex_entire:
- f = self.lex(f.read()).splitlines()
-
- slice = itertools.islice(f, start - 1, stop - 1)
-
- for idx, line in enumerate(slice):
- if start + idx == mark_line:
- prefix = '>'
- else:
- prefix = ' '
-
- if lex_source and not lex_entire:
- line = self.lex(line)
-
- yield '%s %4d %s' % (prefix, start + idx, line.rstrip())
-
- def get_source(self, start, stop=None, lex_source=True, mark_line=0,
- lex_entire=False):
- exc = gdb.GdbError('Unable to retrieve source code')
-
- if not self.filename:
- raise exc
-
- start = max(start, 1)
- if stop is None:
- stop = start + 1
-
- try:
- return '\n'.join(
- self._get_source(start, stop, lex_source, mark_line, lex_entire))
- except IOError:
- raise exc
-
-
-# Errors
-
-class CyGDBError(gdb.GdbError):
- """
+ return True
+ return False
+
+ return cur_lineno > cyvar.lineno
+
+
+class SourceFileDescriptor(object):
+ def __init__(self, filename, lexer, formatter=None):
+ self.filename = filename
+ self.lexer = lexer
+ self.formatter = formatter
+
+ def valid(self):
+ return self.filename is not None
+
+ def lex(self, code):
+ if pygments and self.lexer and parameters.colorize_code:
+ bg = parameters.terminal_background.value
+ if self.formatter is None:
+ formatter = pygments.formatters.TerminalFormatter(bg=bg)
+ else:
+ formatter = self.formatter
+
+ return pygments.highlight(code, self.lexer, formatter)
+
+ return code
+
+ def _get_source(self, start, stop, lex_source, mark_line, lex_entire):
+ with open(self.filename) as f:
+ # to provide "correct" colouring, the entire code needs to be
+ # lexed. However, this makes a lot of things terribly slow, so
+ # we decide not to. Besides, it's unlikely to matter.
+
+ if lex_source and lex_entire:
+ f = self.lex(f.read()).splitlines()
+
+ slice = itertools.islice(f, start - 1, stop - 1)
+
+ for idx, line in enumerate(slice):
+ if start + idx == mark_line:
+ prefix = '>'
+ else:
+ prefix = ' '
+
+ if lex_source and not lex_entire:
+ line = self.lex(line)
+
+ yield '%s %4d %s' % (prefix, start + idx, line.rstrip())
+
+ def get_source(self, start, stop=None, lex_source=True, mark_line=0,
+ lex_entire=False):
+ exc = gdb.GdbError('Unable to retrieve source code')
+
+ if not self.filename:
+ raise exc
+
+ start = max(start, 1)
+ if stop is None:
+ stop = start + 1
+
+ try:
+ return '\n'.join(
+ self._get_source(start, stop, lex_source, mark_line, lex_entire))
+ except IOError:
+ raise exc
+
+
+# Errors
+
+class CyGDBError(gdb.GdbError):
+ """
Base class for Cython-command related errors
- """
-
- def __init__(self, *args):
- args = args or (self.msg,)
- super(CyGDBError, self).__init__(*args)
-
-
-class NoCythonFunctionInFrameError(CyGDBError):
- """
- raised when the user requests the current cython function, which is
- unavailable
- """
- msg = "Current function is a function cygdb doesn't know about"
-
-
-class NoFunctionNameInFrameError(NoCythonFunctionInFrameError):
- """
- raised when the name of the C function could not be determined
- in the current C stack frame
- """
- msg = ('C function name could not be determined in the current C stack '
- 'frame')
-
-
-# Parameters
-
-class CythonParameter(gdb.Parameter):
- """
- Base class for cython parameters
- """
-
- def __init__(self, name, command_class, parameter_class, default=None):
- self.show_doc = self.set_doc = self.__class__.__doc__
- super(CythonParameter, self).__init__(name, command_class,
- parameter_class)
- if default is not None:
- self.value = default
-
- def __bool__(self):
- return bool(self.value)
-
- __nonzero__ = __bool__ # Python 2
-
-
-
-class CompleteUnqualifiedFunctionNames(CythonParameter):
- """
- Have 'cy break' complete unqualified function or method names.
- """
-
-
-class ColorizeSourceCode(CythonParameter):
- """
- Tell cygdb whether to colorize source code.
- """
-
-
-class TerminalBackground(CythonParameter):
- """
- Tell cygdb about the user's terminal background (light or dark).
- """
-
-
-class CythonParameters(object):
- """
- Simple container class that might get more functionality in the distant
- future (mostly to remind us that we're dealing with parameters).
- """
-
- def __init__(self):
- self.complete_unqualified = CompleteUnqualifiedFunctionNames(
- 'cy_complete_unqualified',
- gdb.COMMAND_BREAKPOINTS,
- gdb.PARAM_BOOLEAN,
- True)
- self.colorize_code = ColorizeSourceCode(
- 'cy_colorize_code',
- gdb.COMMAND_FILES,
- gdb.PARAM_BOOLEAN,
- True)
- self.terminal_background = TerminalBackground(
- 'cy_terminal_background_color',
- gdb.COMMAND_FILES,
- gdb.PARAM_STRING,
- "dark")
-
-parameters = CythonParameters()
-
-
-# Commands
-
-class CythonCommand(gdb.Command, CythonBase):
- """
- Base class for Cython commands
- """
-
- command_class = gdb.COMMAND_NONE
-
- @classmethod
- def _register(cls, clsname, args, kwargs):
- if not hasattr(cls, 'completer_class'):
- return cls(clsname, cls.command_class, *args, **kwargs)
- else:
- return cls(clsname, cls.command_class, cls.completer_class,
- *args, **kwargs)
-
- @classmethod
- def register(cls, *args, **kwargs):
- alias = getattr(cls, 'alias', None)
- if alias:
- cls._register(cls.alias, args, kwargs)
-
- return cls._register(cls.name, args, kwargs)
-
-
-class CyCy(CythonCommand):
- """
- Invoke a Cython command. Available commands are:
-
- cy import
- cy break
- cy step
- cy next
- cy run
- cy cont
- cy finish
- cy up
- cy down
- cy select
- cy bt / cy backtrace
- cy list
- cy print
- cy set
- cy locals
- cy globals
- cy exec
- """
-
- name = 'cy'
- command_class = gdb.COMMAND_NONE
- completer_class = gdb.COMPLETE_COMMAND
-
- def __init__(self, name, command_class, completer_class):
- # keep the signature 2.5 compatible (i.e. do not use f(*a, k=v)
- super(CythonCommand, self).__init__(name, command_class,
- completer_class, prefix=True)
-
- commands = dict(
- # GDB commands
- import_ = CyImport.register(),
- break_ = CyBreak.register(),
- step = CyStep.register(),
- next = CyNext.register(),
- run = CyRun.register(),
- cont = CyCont.register(),
- finish = CyFinish.register(),
- up = CyUp.register(),
- down = CyDown.register(),
- select = CySelect.register(),
- bt = CyBacktrace.register(),
- list = CyList.register(),
- print_ = CyPrint.register(),
- locals = CyLocals.register(),
- globals = CyGlobals.register(),
- exec_ = libpython.FixGdbCommand('cy exec', '-cy-exec'),
- _exec = CyExec.register(),
- set = CySet.register(),
-
- # GDB functions
- cy_cname = CyCName('cy_cname'),
- cy_cvalue = CyCValue('cy_cvalue'),
- cy_lineno = CyLine('cy_lineno'),
- cy_eval = CyEval('cy_eval'),
- )
-
- for command_name, command in commands.items():
- command.cy = self
- setattr(self, command_name, command)
-
- self.cy = self
-
- # Cython module namespace
- self.cython_namespace = {}
-
- # maps (unique) qualified function names (e.g.
- # cythonmodule.ClassName.method_name) to the CythonFunction object
- self.functions_by_qualified_name = {}
-
- # unique cnames of Cython functions
- self.functions_by_cname = {}
-
- # map function names like method_name to a list of all such
- # CythonFunction objects
- self.functions_by_name = collections.defaultdict(list)
-
-
-class CyImport(CythonCommand):
- """
- Import debug information outputted by the Cython compiler
- Example: cy import FILE...
- """
-
- name = 'cy import'
- command_class = gdb.COMMAND_STATUS
- completer_class = gdb.COMPLETE_FILENAME
-
- def invoke(self, args, from_tty):
+ """
+
+ def __init__(self, *args):
+ args = args or (self.msg,)
+ super(CyGDBError, self).__init__(*args)
+
+
+class NoCythonFunctionInFrameError(CyGDBError):
+ """
+ raised when the user requests the current cython function, which is
+ unavailable
+ """
+ msg = "Current function is a function cygdb doesn't know about"
+
+
+class NoFunctionNameInFrameError(NoCythonFunctionInFrameError):
+ """
+ raised when the name of the C function could not be determined
+ in the current C stack frame
+ """
+ msg = ('C function name could not be determined in the current C stack '
+ 'frame')
+
+
+# Parameters
+
+class CythonParameter(gdb.Parameter):
+ """
+ Base class for cython parameters
+ """
+
+ def __init__(self, name, command_class, parameter_class, default=None):
+ self.show_doc = self.set_doc = self.__class__.__doc__
+ super(CythonParameter, self).__init__(name, command_class,
+ parameter_class)
+ if default is not None:
+ self.value = default
+
+ def __bool__(self):
+ return bool(self.value)
+
+ __nonzero__ = __bool__ # Python 2
+
+
+
+class CompleteUnqualifiedFunctionNames(CythonParameter):
+ """
+ Have 'cy break' complete unqualified function or method names.
+ """
+
+
+class ColorizeSourceCode(CythonParameter):
+ """
+ Tell cygdb whether to colorize source code.
+ """
+
+
+class TerminalBackground(CythonParameter):
+ """
+ Tell cygdb about the user's terminal background (light or dark).
+ """
+
+
+class CythonParameters(object):
+ """
+ Simple container class that might get more functionality in the distant
+ future (mostly to remind us that we're dealing with parameters).
+ """
+
+ def __init__(self):
+ self.complete_unqualified = CompleteUnqualifiedFunctionNames(
+ 'cy_complete_unqualified',
+ gdb.COMMAND_BREAKPOINTS,
+ gdb.PARAM_BOOLEAN,
+ True)
+ self.colorize_code = ColorizeSourceCode(
+ 'cy_colorize_code',
+ gdb.COMMAND_FILES,
+ gdb.PARAM_BOOLEAN,
+ True)
+ self.terminal_background = TerminalBackground(
+ 'cy_terminal_background_color',
+ gdb.COMMAND_FILES,
+ gdb.PARAM_STRING,
+ "dark")
+
+parameters = CythonParameters()
+
+
+# Commands
+
+class CythonCommand(gdb.Command, CythonBase):
+ """
+ Base class for Cython commands
+ """
+
+ command_class = gdb.COMMAND_NONE
+
+ @classmethod
+ def _register(cls, clsname, args, kwargs):
+ if not hasattr(cls, 'completer_class'):
+ return cls(clsname, cls.command_class, *args, **kwargs)
+ else:
+ return cls(clsname, cls.command_class, cls.completer_class,
+ *args, **kwargs)
+
+ @classmethod
+ def register(cls, *args, **kwargs):
+ alias = getattr(cls, 'alias', None)
+ if alias:
+ cls._register(cls.alias, args, kwargs)
+
+ return cls._register(cls.name, args, kwargs)
+
+
+class CyCy(CythonCommand):
+ """
+ Invoke a Cython command. Available commands are:
+
+ cy import
+ cy break
+ cy step
+ cy next
+ cy run
+ cy cont
+ cy finish
+ cy up
+ cy down
+ cy select
+ cy bt / cy backtrace
+ cy list
+ cy print
+ cy set
+ cy locals
+ cy globals
+ cy exec
+ """
+
+ name = 'cy'
+ command_class = gdb.COMMAND_NONE
+ completer_class = gdb.COMPLETE_COMMAND
+
+ def __init__(self, name, command_class, completer_class):
+ # keep the signature 2.5 compatible (i.e. do not use f(*a, k=v)
+ super(CythonCommand, self).__init__(name, command_class,
+ completer_class, prefix=True)
+
+ commands = dict(
+ # GDB commands
+ import_ = CyImport.register(),
+ break_ = CyBreak.register(),
+ step = CyStep.register(),
+ next = CyNext.register(),
+ run = CyRun.register(),
+ cont = CyCont.register(),
+ finish = CyFinish.register(),
+ up = CyUp.register(),
+ down = CyDown.register(),
+ select = CySelect.register(),
+ bt = CyBacktrace.register(),
+ list = CyList.register(),
+ print_ = CyPrint.register(),
+ locals = CyLocals.register(),
+ globals = CyGlobals.register(),
+ exec_ = libpython.FixGdbCommand('cy exec', '-cy-exec'),
+ _exec = CyExec.register(),
+ set = CySet.register(),
+
+ # GDB functions
+ cy_cname = CyCName('cy_cname'),
+ cy_cvalue = CyCValue('cy_cvalue'),
+ cy_lineno = CyLine('cy_lineno'),
+ cy_eval = CyEval('cy_eval'),
+ )
+
+ for command_name, command in commands.items():
+ command.cy = self
+ setattr(self, command_name, command)
+
+ self.cy = self
+
+ # Cython module namespace
+ self.cython_namespace = {}
+
+ # maps (unique) qualified function names (e.g.
+ # cythonmodule.ClassName.method_name) to the CythonFunction object
+ self.functions_by_qualified_name = {}
+
+ # unique cnames of Cython functions
+ self.functions_by_cname = {}
+
+ # map function names like method_name to a list of all such
+ # CythonFunction objects
+ self.functions_by_name = collections.defaultdict(list)
+
+
+class CyImport(CythonCommand):
+ """
+ Import debug information outputted by the Cython compiler
+ Example: cy import FILE...
+ """
+
+ name = 'cy import'
+ command_class = gdb.COMMAND_STATUS
+ completer_class = gdb.COMPLETE_FILENAME
+
+ def invoke(self, args, from_tty):
if isinstance(args, BYTES):
args = args.decode(_filesystemencoding)
- for arg in string_to_argv(args):
- try:
- f = open(arg)
- except OSError as e:
- raise gdb.GdbError('Unable to open file %r: %s' % (args, e.args[1]))
-
- t = etree.parse(f)
-
- for module in t.getroot():
- cython_module = CythonModule(**module.attrib)
- self.cy.cython_namespace[cython_module.name] = cython_module
-
- for variable in module.find('Globals'):
- d = variable.attrib
- cython_module.globals[d['name']] = CythonVariable(**d)
-
- for function in module.find('Functions'):
- cython_function = CythonFunction(module=cython_module,
- **function.attrib)
-
- # update the global function mappings
- name = cython_function.name
- qname = cython_function.qualified_name
-
- self.cy.functions_by_name[name].append(cython_function)
- self.cy.functions_by_qualified_name[
- cython_function.qualified_name] = cython_function
- self.cy.functions_by_cname[
- cython_function.cname] = cython_function
-
- d = cython_module.functions[qname] = cython_function
-
- for local in function.find('Locals'):
- d = local.attrib
- cython_function.locals[d['name']] = CythonVariable(**d)
-
- for step_into_func in function.find('StepIntoFunctions'):
- d = step_into_func.attrib
- cython_function.step_into_functions.add(d['name'])
-
- cython_function.arguments.extend(
- funcarg.tag for funcarg in function.find('Arguments'))
-
- for marker in module.find('LineNumberMapping'):
- cython_lineno = int(marker.attrib['cython_lineno'])
+ for arg in string_to_argv(args):
+ try:
+ f = open(arg)
+ except OSError as e:
+ raise gdb.GdbError('Unable to open file %r: %s' % (args, e.args[1]))
+
+ t = etree.parse(f)
+
+ for module in t.getroot():
+ cython_module = CythonModule(**module.attrib)
+ self.cy.cython_namespace[cython_module.name] = cython_module
+
+ for variable in module.find('Globals'):
+ d = variable.attrib
+ cython_module.globals[d['name']] = CythonVariable(**d)
+
+ for function in module.find('Functions'):
+ cython_function = CythonFunction(module=cython_module,
+ **function.attrib)
+
+ # update the global function mappings
+ name = cython_function.name
+ qname = cython_function.qualified_name
+
+ self.cy.functions_by_name[name].append(cython_function)
+ self.cy.functions_by_qualified_name[
+ cython_function.qualified_name] = cython_function
+ self.cy.functions_by_cname[
+ cython_function.cname] = cython_function
+
+ d = cython_module.functions[qname] = cython_function
+
+ for local in function.find('Locals'):
+ d = local.attrib
+ cython_function.locals[d['name']] = CythonVariable(**d)
+
+ for step_into_func in function.find('StepIntoFunctions'):
+ d = step_into_func.attrib
+ cython_function.step_into_functions.add(d['name'])
+
+ cython_function.arguments.extend(
+ funcarg.tag for funcarg in function.find('Arguments'))
+
+ for marker in module.find('LineNumberMapping'):
+ cython_lineno = int(marker.attrib['cython_lineno'])
c_linenos = list(map(int, marker.attrib['c_linenos'].split()))
- cython_module.lineno_cy2c[cython_lineno] = min(c_linenos)
- for c_lineno in c_linenos:
- cython_module.lineno_c2cy[c_lineno] = cython_lineno
-
-
-class CyBreak(CythonCommand):
- """
- Set a breakpoint for Cython code using Cython qualified name notation, e.g.:
-
- cy break cython_modulename.ClassName.method_name...
-
- or normal notation:
-
- cy break function_or_method_name...
-
- or for a line number:
-
- cy break cython_module:lineno...
-
- Set a Python breakpoint:
- Break on any function or method named 'func' in module 'modname'
-
- cy break -p modname.func...
-
- Break on any function or method named 'func'
-
- cy break -p func...
- """
-
- name = 'cy break'
- command_class = gdb.COMMAND_BREAKPOINTS
-
- def _break_pyx(self, name):
- modulename, _, lineno = name.partition(':')
- lineno = int(lineno)
- if modulename:
- cython_module = self.cy.cython_namespace[modulename]
- else:
- cython_module = self.get_cython_function().module
-
- if lineno in cython_module.lineno_cy2c:
- c_lineno = cython_module.lineno_cy2c[lineno]
- breakpoint = '%s:%s' % (cython_module.c_filename, c_lineno)
- gdb.execute('break ' + breakpoint)
- else:
- raise gdb.GdbError("Not a valid line number. "
- "Does it contain actual code?")
-
- def _break_funcname(self, funcname):
- func = self.cy.functions_by_qualified_name.get(funcname)
-
- if func and func.is_initmodule_function:
- func = None
-
- break_funcs = [func]
-
- if not func:
- funcs = self.cy.functions_by_name.get(funcname) or []
- funcs = [f for f in funcs if not f.is_initmodule_function]
-
- if not funcs:
- gdb.execute('break ' + funcname)
- return
-
- if len(funcs) > 1:
- # multiple functions, let the user pick one
- print('There are multiple such functions:')
- for idx, func in enumerate(funcs):
- print('%3d) %s' % (idx, func.qualified_name))
-
- while True:
- try:
+ cython_module.lineno_cy2c[cython_lineno] = min(c_linenos)
+ for c_lineno in c_linenos:
+ cython_module.lineno_c2cy[c_lineno] = cython_lineno
+
+
+class CyBreak(CythonCommand):
+ """
+ Set a breakpoint for Cython code using Cython qualified name notation, e.g.:
+
+ cy break cython_modulename.ClassName.method_name...
+
+ or normal notation:
+
+ cy break function_or_method_name...
+
+ or for a line number:
+
+ cy break cython_module:lineno...
+
+ Set a Python breakpoint:
+ Break on any function or method named 'func' in module 'modname'
+
+ cy break -p modname.func...
+
+ Break on any function or method named 'func'
+
+ cy break -p func...
+ """
+
+ name = 'cy break'
+ command_class = gdb.COMMAND_BREAKPOINTS
+
+ def _break_pyx(self, name):
+ modulename, _, lineno = name.partition(':')
+ lineno = int(lineno)
+ if modulename:
+ cython_module = self.cy.cython_namespace[modulename]
+ else:
+ cython_module = self.get_cython_function().module
+
+ if lineno in cython_module.lineno_cy2c:
+ c_lineno = cython_module.lineno_cy2c[lineno]
+ breakpoint = '%s:%s' % (cython_module.c_filename, c_lineno)
+ gdb.execute('break ' + breakpoint)
+ else:
+ raise gdb.GdbError("Not a valid line number. "
+ "Does it contain actual code?")
+
+ def _break_funcname(self, funcname):
+ func = self.cy.functions_by_qualified_name.get(funcname)
+
+ if func and func.is_initmodule_function:
+ func = None
+
+ break_funcs = [func]
+
+ if not func:
+ funcs = self.cy.functions_by_name.get(funcname) or []
+ funcs = [f for f in funcs if not f.is_initmodule_function]
+
+ if not funcs:
+ gdb.execute('break ' + funcname)
+ return
+
+ if len(funcs) > 1:
+ # multiple functions, let the user pick one
+ print('There are multiple such functions:')
+ for idx, func in enumerate(funcs):
+ print('%3d) %s' % (idx, func.qualified_name))
+
+ while True:
+ try:
result = input(
- "Select a function, press 'a' for all "
- "functions or press 'q' or '^D' to quit: ")
- except EOFError:
- return
- else:
- if result.lower() == 'q':
- return
- elif result.lower() == 'a':
- break_funcs = funcs
- break
- elif (result.isdigit() and
- 0 <= int(result) < len(funcs)):
- break_funcs = [funcs[int(result)]]
- break
- else:
- print('Not understood...')
- else:
- break_funcs = [funcs[0]]
-
- for func in break_funcs:
- gdb.execute('break %s' % func.cname)
- if func.pf_cname:
- gdb.execute('break %s' % func.pf_cname)
-
- def invoke(self, function_names, from_tty):
+ "Select a function, press 'a' for all "
+ "functions or press 'q' or '^D' to quit: ")
+ except EOFError:
+ return
+ else:
+ if result.lower() == 'q':
+ return
+ elif result.lower() == 'a':
+ break_funcs = funcs
+ break
+ elif (result.isdigit() and
+ 0 <= int(result) < len(funcs)):
+ break_funcs = [funcs[int(result)]]
+ break
+ else:
+ print('Not understood...')
+ else:
+ break_funcs = [funcs[0]]
+
+ for func in break_funcs:
+ gdb.execute('break %s' % func.cname)
+ if func.pf_cname:
+ gdb.execute('break %s' % func.pf_cname)
+
+ def invoke(self, function_names, from_tty):
if isinstance(function_names, BYTES):
function_names = function_names.decode(_filesystemencoding)
argv = string_to_argv(function_names)
- if function_names.startswith('-p'):
- argv = argv[1:]
- python_breakpoints = True
- else:
- python_breakpoints = False
-
- for funcname in argv:
- if python_breakpoints:
- gdb.execute('py-break %s' % funcname)
- elif ':' in funcname:
- self._break_pyx(funcname)
- else:
- self._break_funcname(funcname)
-
- @dont_suppress_errors
- def complete(self, text, word):
- # Filter init-module functions (breakpoints can be set using
- # modulename:linenumber).
+ if function_names.startswith('-p'):
+ argv = argv[1:]
+ python_breakpoints = True
+ else:
+ python_breakpoints = False
+
+ for funcname in argv:
+ if python_breakpoints:
+ gdb.execute('py-break %s' % funcname)
+ elif ':' in funcname:
+ self._break_pyx(funcname)
+ else:
+ self._break_funcname(funcname)
+
+ @dont_suppress_errors
+ def complete(self, text, word):
+ # Filter init-module functions (breakpoints can be set using
+ # modulename:linenumber).
names = [n for n, L in self.cy.functions_by_name.items()
if any(not f.is_initmodule_function for f in L)]
qnames = [n for n, f in self.cy.functions_by_qualified_name.items()
if not f.is_initmodule_function]
-
- if parameters.complete_unqualified:
- all_names = itertools.chain(qnames, names)
- else:
- all_names = qnames
-
- words = text.strip().split()
- if not words or '.' not in words[-1]:
- # complete unqualified
- seen = set(text[:-len(word)].split())
- return [n for n in all_names
- if n.startswith(word) and n not in seen]
-
- # complete qualified name
- lastword = words[-1]
- compl = [n for n in qnames if n.startswith(lastword)]
-
- if len(lastword) > len(word):
- # readline sees something (e.g. a '.') as a word boundary, so don't
- # "recomplete" this prefix
- strip_prefix_length = len(lastword) - len(word)
- compl = [n[strip_prefix_length:] for n in compl]
-
- return compl
-
-
-class CythonInfo(CythonBase, libpython.PythonInfo):
- """
- Implementation of the interface dictated by libpython.LanguageInfo.
- """
-
- def lineno(self, frame):
- # Take care of the Python and Cython levels. We need to care for both
+
+ if parameters.complete_unqualified:
+ all_names = itertools.chain(qnames, names)
+ else:
+ all_names = qnames
+
+ words = text.strip().split()
+ if not words or '.' not in words[-1]:
+ # complete unqualified
+ seen = set(text[:-len(word)].split())
+ return [n for n in all_names
+ if n.startswith(word) and n not in seen]
+
+ # complete qualified name
+ lastword = words[-1]
+ compl = [n for n in qnames if n.startswith(lastword)]
+
+ if len(lastword) > len(word):
+ # readline sees something (e.g. a '.') as a word boundary, so don't
+ # "recomplete" this prefix
+ strip_prefix_length = len(lastword) - len(word)
+ compl = [n[strip_prefix_length:] for n in compl]
+
+ return compl
+
+
+class CythonInfo(CythonBase, libpython.PythonInfo):
+ """
+ Implementation of the interface dictated by libpython.LanguageInfo.
+ """
+
+ def lineno(self, frame):
+ # Take care of the Python and Cython levels. We need to care for both
# as we can't simply dispatch to 'py-step', since that would work for
- # stepping through Python code, but it would not step back into Cython-
- # related code. The C level should be dispatched to the 'step' command.
- if self.is_cython_function(frame):
- return self.get_cython_lineno(frame)
- return super(CythonInfo, self).lineno(frame)
-
- def get_source_line(self, frame):
- try:
- line = super(CythonInfo, self).get_source_line(frame)
- except gdb.GdbError:
- return None
- else:
- return line.strip() or None
-
- def exc_info(self, frame):
- if self.is_python_function:
- return super(CythonInfo, self).exc_info(frame)
-
- def runtime_break_functions(self):
- if self.is_cython_function():
- return self.get_cython_function().step_into_functions
- return ()
-
- def static_break_functions(self):
- result = ['PyEval_EvalFrameEx']
- result.extend(self.cy.functions_by_cname)
- return result
-
-
-class CythonExecutionControlCommand(CythonCommand,
- libpython.ExecutionControlCommandBase):
-
- @classmethod
- def register(cls):
- return cls(cls.name, cython_info)
-
-
-class CyStep(CythonExecutionControlCommand, libpython.PythonStepperMixin):
- "Step through Cython, Python or C code."
-
- name = 'cy -step'
- stepinto = True
-
- def invoke(self, args, from_tty):
- if self.is_python_function():
- self.python_step(self.stepinto)
- elif not self.is_cython_function():
- if self.stepinto:
- command = 'step'
- else:
- command = 'next'
-
- self.finish_executing(gdb.execute(command, to_string=True))
- else:
- self.step(stepinto=self.stepinto)
-
-
-class CyNext(CyStep):
- "Step-over Cython, Python or C code."
-
- name = 'cy -next'
- stepinto = False
-
-
-class CyRun(CythonExecutionControlCommand):
- """
- Run a Cython program. This is like the 'run' command, except that it
- displays Cython or Python source lines as well
- """
-
- name = 'cy run'
-
- invoke = CythonExecutionControlCommand.run
-
-
-class CyCont(CythonExecutionControlCommand):
- """
- Continue a Cython program. This is like the 'run' command, except that it
- displays Cython or Python source lines as well.
- """
-
- name = 'cy cont'
- invoke = CythonExecutionControlCommand.cont
-
-
-class CyFinish(CythonExecutionControlCommand):
- """
- Execute until the function returns.
- """
- name = 'cy finish'
-
- invoke = CythonExecutionControlCommand.finish
-
-
-class CyUp(CythonCommand):
- """
- Go up a Cython, Python or relevant C frame.
- """
- name = 'cy up'
- _command = 'up'
-
- def invoke(self, *args):
- try:
- gdb.execute(self._command, to_string=True)
- while not self.is_relevant_function(gdb.selected_frame()):
- gdb.execute(self._command, to_string=True)
- except RuntimeError as e:
- raise gdb.GdbError(*e.args)
-
- frame = gdb.selected_frame()
- index = 0
- while frame:
- frame = frame.older()
- index += 1
-
- self.print_stackframe(index=index - 1)
-
-
-class CyDown(CyUp):
- """
- Go down a Cython, Python or relevant C frame.
- """
-
- name = 'cy down'
- _command = 'down'
-
-
-class CySelect(CythonCommand):
- """
- Select a frame. Use frame numbers as listed in `cy backtrace`.
- This command is useful because `cy backtrace` prints a reversed backtrace.
- """
-
- name = 'cy select'
-
- def invoke(self, stackno, from_tty):
- try:
- stackno = int(stackno)
- except ValueError:
- raise gdb.GdbError("Not a valid number: %r" % (stackno,))
-
- frame = gdb.selected_frame()
- while frame.newer():
- frame = frame.newer()
-
- stackdepth = libpython.stackdepth(frame)
-
- try:
- gdb.execute('select %d' % (stackdepth - stackno - 1,))
- except RuntimeError as e:
- raise gdb.GdbError(*e.args)
-
-
-class CyBacktrace(CythonCommand):
- 'Print the Cython stack'
-
- name = 'cy bt'
- alias = 'cy backtrace'
- command_class = gdb.COMMAND_STACK
- completer_class = gdb.COMPLETE_NONE
-
- @require_running_program
- def invoke(self, args, from_tty):
- # get the first frame
- frame = gdb.selected_frame()
- while frame.older():
- frame = frame.older()
-
- print_all = args == '-a'
-
- index = 0
- while frame:
- try:
- is_relevant = self.is_relevant_function(frame)
- except CyGDBError:
- is_relevant = False
-
- if print_all or is_relevant:
- self.print_stackframe(frame, index)
-
- index += 1
- frame = frame.newer()
-
-
-class CyList(CythonCommand):
- """
- List Cython source code. To disable to customize colouring see the cy_*
- parameters.
- """
-
- name = 'cy list'
- command_class = gdb.COMMAND_FILES
- completer_class = gdb.COMPLETE_NONE
-
- # @dispatch_on_frame(c_command='list')
- def invoke(self, _, from_tty):
- sd, lineno = self.get_source_desc()
- source = sd.get_source(lineno - 5, lineno + 5, mark_line=lineno,
- lex_entire=True)
- print(source)
-
-
-class CyPrint(CythonCommand):
- """
- Print a Cython variable using 'cy-print x' or 'cy-print module.function.x'
- """
-
- name = 'cy print'
- command_class = gdb.COMMAND_DATA
-
- def invoke(self, name, from_tty, max_name_length=None):
- if self.is_python_function():
- return gdb.execute('py-print ' + name)
- elif self.is_cython_function():
- value = self.cy.cy_cvalue.invoke(name.lstrip('*'))
- for c in name:
- if c == '*':
- value = value.dereference()
- else:
- break
-
- self.print_gdb_value(name, value, max_name_length)
- else:
- gdb.execute('print ' + name)
-
- def complete(self):
- if self.is_cython_function():
- f = self.get_cython_function()
- return list(itertools.chain(f.locals, f.globals))
- else:
- return []
-
-
-sortkey = lambda item: item[0].lower()
-
-
-class CyLocals(CythonCommand):
- """
- List the locals from the current Cython frame.
- """
-
- name = 'cy locals'
- command_class = gdb.COMMAND_STACK
- completer_class = gdb.COMPLETE_NONE
-
- @dispatch_on_frame(c_command='info locals', python_command='py-locals')
- def invoke(self, args, from_tty):
- cython_function = self.get_cython_function()
-
- if cython_function.is_initmodule_function:
- self.cy.globals.invoke(args, from_tty)
- return
-
- local_cython_vars = cython_function.locals
- max_name_length = len(max(local_cython_vars, key=len))
+ # stepping through Python code, but it would not step back into Cython-
+ # related code. The C level should be dispatched to the 'step' command.
+ if self.is_cython_function(frame):
+ return self.get_cython_lineno(frame)
+ return super(CythonInfo, self).lineno(frame)
+
+ def get_source_line(self, frame):
+ try:
+ line = super(CythonInfo, self).get_source_line(frame)
+ except gdb.GdbError:
+ return None
+ else:
+ return line.strip() or None
+
+ def exc_info(self, frame):
+ if self.is_python_function:
+ return super(CythonInfo, self).exc_info(frame)
+
+ def runtime_break_functions(self):
+ if self.is_cython_function():
+ return self.get_cython_function().step_into_functions
+ return ()
+
+ def static_break_functions(self):
+ result = ['PyEval_EvalFrameEx']
+ result.extend(self.cy.functions_by_cname)
+ return result
+
+
+class CythonExecutionControlCommand(CythonCommand,
+ libpython.ExecutionControlCommandBase):
+
+ @classmethod
+ def register(cls):
+ return cls(cls.name, cython_info)
+
+
+class CyStep(CythonExecutionControlCommand, libpython.PythonStepperMixin):
+ "Step through Cython, Python or C code."
+
+ name = 'cy -step'
+ stepinto = True
+
+ def invoke(self, args, from_tty):
+ if self.is_python_function():
+ self.python_step(self.stepinto)
+ elif not self.is_cython_function():
+ if self.stepinto:
+ command = 'step'
+ else:
+ command = 'next'
+
+ self.finish_executing(gdb.execute(command, to_string=True))
+ else:
+ self.step(stepinto=self.stepinto)
+
+
+class CyNext(CyStep):
+ "Step-over Cython, Python or C code."
+
+ name = 'cy -next'
+ stepinto = False
+
+
+class CyRun(CythonExecutionControlCommand):
+ """
+ Run a Cython program. This is like the 'run' command, except that it
+ displays Cython or Python source lines as well
+ """
+
+ name = 'cy run'
+
+ invoke = CythonExecutionControlCommand.run
+
+
+class CyCont(CythonExecutionControlCommand):
+ """
+ Continue a Cython program. This is like the 'run' command, except that it
+ displays Cython or Python source lines as well.
+ """
+
+ name = 'cy cont'
+ invoke = CythonExecutionControlCommand.cont
+
+
+class CyFinish(CythonExecutionControlCommand):
+ """
+ Execute until the function returns.
+ """
+ name = 'cy finish'
+
+ invoke = CythonExecutionControlCommand.finish
+
+
+class CyUp(CythonCommand):
+ """
+ Go up a Cython, Python or relevant C frame.
+ """
+ name = 'cy up'
+ _command = 'up'
+
+ def invoke(self, *args):
+ try:
+ gdb.execute(self._command, to_string=True)
+ while not self.is_relevant_function(gdb.selected_frame()):
+ gdb.execute(self._command, to_string=True)
+ except RuntimeError as e:
+ raise gdb.GdbError(*e.args)
+
+ frame = gdb.selected_frame()
+ index = 0
+ while frame:
+ frame = frame.older()
+ index += 1
+
+ self.print_stackframe(index=index - 1)
+
+
+class CyDown(CyUp):
+ """
+ Go down a Cython, Python or relevant C frame.
+ """
+
+ name = 'cy down'
+ _command = 'down'
+
+
+class CySelect(CythonCommand):
+ """
+ Select a frame. Use frame numbers as listed in `cy backtrace`.
+ This command is useful because `cy backtrace` prints a reversed backtrace.
+ """
+
+ name = 'cy select'
+
+ def invoke(self, stackno, from_tty):
+ try:
+ stackno = int(stackno)
+ except ValueError:
+ raise gdb.GdbError("Not a valid number: %r" % (stackno,))
+
+ frame = gdb.selected_frame()
+ while frame.newer():
+ frame = frame.newer()
+
+ stackdepth = libpython.stackdepth(frame)
+
+ try:
+ gdb.execute('select %d' % (stackdepth - stackno - 1,))
+ except RuntimeError as e:
+ raise gdb.GdbError(*e.args)
+
+
+class CyBacktrace(CythonCommand):
+ 'Print the Cython stack'
+
+ name = 'cy bt'
+ alias = 'cy backtrace'
+ command_class = gdb.COMMAND_STACK
+ completer_class = gdb.COMPLETE_NONE
+
+ @require_running_program
+ def invoke(self, args, from_tty):
+ # get the first frame
+ frame = gdb.selected_frame()
+ while frame.older():
+ frame = frame.older()
+
+ print_all = args == '-a'
+
+ index = 0
+ while frame:
+ try:
+ is_relevant = self.is_relevant_function(frame)
+ except CyGDBError:
+ is_relevant = False
+
+ if print_all or is_relevant:
+ self.print_stackframe(frame, index)
+
+ index += 1
+ frame = frame.newer()
+
+
+class CyList(CythonCommand):
+ """
+ List Cython source code. To disable to customize colouring see the cy_*
+ parameters.
+ """
+
+ name = 'cy list'
+ command_class = gdb.COMMAND_FILES
+ completer_class = gdb.COMPLETE_NONE
+
+ # @dispatch_on_frame(c_command='list')
+ def invoke(self, _, from_tty):
+ sd, lineno = self.get_source_desc()
+ source = sd.get_source(lineno - 5, lineno + 5, mark_line=lineno,
+ lex_entire=True)
+ print(source)
+
+
+class CyPrint(CythonCommand):
+ """
+ Print a Cython variable using 'cy-print x' or 'cy-print module.function.x'
+ """
+
+ name = 'cy print'
+ command_class = gdb.COMMAND_DATA
+
+ def invoke(self, name, from_tty, max_name_length=None):
+ if self.is_python_function():
+ return gdb.execute('py-print ' + name)
+ elif self.is_cython_function():
+ value = self.cy.cy_cvalue.invoke(name.lstrip('*'))
+ for c in name:
+ if c == '*':
+ value = value.dereference()
+ else:
+ break
+
+ self.print_gdb_value(name, value, max_name_length)
+ else:
+ gdb.execute('print ' + name)
+
+ def complete(self):
+ if self.is_cython_function():
+ f = self.get_cython_function()
+ return list(itertools.chain(f.locals, f.globals))
+ else:
+ return []
+
+
+sortkey = lambda item: item[0].lower()
+
+
+class CyLocals(CythonCommand):
+ """
+ List the locals from the current Cython frame.
+ """
+
+ name = 'cy locals'
+ command_class = gdb.COMMAND_STACK
+ completer_class = gdb.COMPLETE_NONE
+
+ @dispatch_on_frame(c_command='info locals', python_command='py-locals')
+ def invoke(self, args, from_tty):
+ cython_function = self.get_cython_function()
+
+ if cython_function.is_initmodule_function:
+ self.cy.globals.invoke(args, from_tty)
+ return
+
+ local_cython_vars = cython_function.locals
+ max_name_length = len(max(local_cython_vars, key=len))
for name, cyvar in sorted(local_cython_vars.items(), key=sortkey):
- if self.is_initialized(self.get_cython_function(), cyvar.name):
- value = gdb.parse_and_eval(cyvar.cname)
- if not value.is_optimized_out:
- self.print_gdb_value(cyvar.name, value,
- max_name_length, '')
-
-
-class CyGlobals(CyLocals):
- """
- List the globals from the current Cython module.
- """
-
- name = 'cy globals'
- command_class = gdb.COMMAND_STACK
- completer_class = gdb.COMPLETE_NONE
-
- @dispatch_on_frame(c_command='info variables', python_command='py-globals')
- def invoke(self, args, from_tty):
- global_python_dict = self.get_cython_globals_dict()
- module_globals = self.get_cython_function().module.globals
-
- max_globals_len = 0
- max_globals_dict_len = 0
- if module_globals:
- max_globals_len = len(max(module_globals, key=len))
- if global_python_dict:
- max_globals_dict_len = len(max(global_python_dict))
-
- max_name_length = max(max_globals_len, max_globals_dict_len)
-
- seen = set()
- print('Python globals:')
+ if self.is_initialized(self.get_cython_function(), cyvar.name):
+ value = gdb.parse_and_eval(cyvar.cname)
+ if not value.is_optimized_out:
+ self.print_gdb_value(cyvar.name, value,
+ max_name_length, '')
+
+
+class CyGlobals(CyLocals):
+ """
+ List the globals from the current Cython module.
+ """
+
+ name = 'cy globals'
+ command_class = gdb.COMMAND_STACK
+ completer_class = gdb.COMPLETE_NONE
+
+ @dispatch_on_frame(c_command='info variables', python_command='py-globals')
+ def invoke(self, args, from_tty):
+ global_python_dict = self.get_cython_globals_dict()
+ module_globals = self.get_cython_function().module.globals
+
+ max_globals_len = 0
+ max_globals_dict_len = 0
+ if module_globals:
+ max_globals_len = len(max(module_globals, key=len))
+ if global_python_dict:
+ max_globals_dict_len = len(max(global_python_dict))
+
+ max_name_length = max(max_globals_len, max_globals_dict_len)
+
+ seen = set()
+ print('Python globals:')
for k, v in sorted(global_python_dict.items(), key=sortkey):
- v = v.get_truncated_repr(libpython.MAX_OUTPUT_LEN)
- seen.add(k)
- print(' %-*s = %s' % (max_name_length, k, v))
-
- print('C globals:')
+ v = v.get_truncated_repr(libpython.MAX_OUTPUT_LEN)
+ seen.add(k)
+ print(' %-*s = %s' % (max_name_length, k, v))
+
+ print('C globals:')
for name, cyvar in sorted(module_globals.items(), key=sortkey):
- if name not in seen:
- try:
- value = gdb.parse_and_eval(cyvar.cname)
- except RuntimeError:
- pass
- else:
- if not value.is_optimized_out:
- self.print_gdb_value(cyvar.name, value,
- max_name_length, ' ')
-
-
-class EvaluateOrExecuteCodeMixin(object):
- """
- Evaluate or execute Python code in a Cython or Python frame. The 'evalcode'
- method evaluations Python code, prints a traceback if an exception went
- uncaught, and returns any return value as a gdb.Value (NULL on exception).
- """
-
- def _fill_locals_dict(self, executor, local_dict_pointer):
- "Fill a remotely allocated dict with values from the Cython C stack"
- cython_func = self.get_cython_function()
-
+ if name not in seen:
+ try:
+ value = gdb.parse_and_eval(cyvar.cname)
+ except RuntimeError:
+ pass
+ else:
+ if not value.is_optimized_out:
+ self.print_gdb_value(cyvar.name, value,
+ max_name_length, ' ')
+
+
+class EvaluateOrExecuteCodeMixin(object):
+ """
+ Evaluate or execute Python code in a Cython or Python frame. The 'evalcode'
+ method evaluations Python code, prints a traceback if an exception went
+ uncaught, and returns any return value as a gdb.Value (NULL on exception).
+ """
+
+ def _fill_locals_dict(self, executor, local_dict_pointer):
+ "Fill a remotely allocated dict with values from the Cython C stack"
+ cython_func = self.get_cython_function()
+
for name, cyvar in cython_func.locals.items():
if cyvar.type == PythonObject and self.is_initialized(cython_func, name):
- try:
- val = gdb.parse_and_eval(cyvar.cname)
- except RuntimeError:
- continue
- else:
- if val.is_optimized_out:
- continue
-
- pystringp = executor.alloc_pystring(name)
- code = '''
- (PyObject *) PyDict_SetItem(
- (PyObject *) %d,
- (PyObject *) %d,
- (PyObject *) %s)
- ''' % (local_dict_pointer, pystringp, cyvar.cname)
-
- try:
- if gdb.parse_and_eval(code) < 0:
- gdb.parse_and_eval('PyErr_Print()')
- raise gdb.GdbError("Unable to execute Python code.")
- finally:
- # PyDict_SetItem doesn't steal our reference
- executor.xdecref(pystringp)
-
- def _find_first_cython_or_python_frame(self):
- frame = gdb.selected_frame()
- while frame:
- if (self.is_cython_function(frame) or
- self.is_python_function(frame)):
- frame.select()
- return frame
-
- frame = frame.older()
-
- raise gdb.GdbError("There is no Cython or Python frame on the stack.")
-
- def _evalcode_cython(self, executor, code, input_type):
- with libpython.FetchAndRestoreError():
- # get the dict of Cython globals and construct a dict in the
- # inferior with Cython locals
- global_dict = gdb.parse_and_eval(
- '(PyObject *) PyModule_GetDict(__pyx_m)')
- local_dict = gdb.parse_and_eval('(PyObject *) PyDict_New()')
-
- try:
- self._fill_locals_dict(executor,
- libpython.pointervalue(local_dict))
- result = executor.evalcode(code, input_type, global_dict,
- local_dict)
- finally:
- executor.xdecref(libpython.pointervalue(local_dict))
-
- return result
-
- def evalcode(self, code, input_type):
- """
- Evaluate `code` in a Python or Cython stack frame using the given
- `input_type`.
- """
- frame = self._find_first_cython_or_python_frame()
- executor = libpython.PythonCodeExecutor()
- if self.is_python_function(frame):
- return libpython._evalcode_python(executor, code, input_type)
- return self._evalcode_cython(executor, code, input_type)
-
-
-class CyExec(CythonCommand, libpython.PyExec, EvaluateOrExecuteCodeMixin):
- """
- Execute Python code in the nearest Python or Cython frame.
- """
-
- name = '-cy-exec'
- command_class = gdb.COMMAND_STACK
- completer_class = gdb.COMPLETE_NONE
-
- def invoke(self, expr, from_tty):
- expr, input_type = self.readcode(expr)
- executor = libpython.PythonCodeExecutor()
- executor.xdecref(self.evalcode(expr, executor.Py_single_input))
-
-
-class CySet(CythonCommand):
- """
- Set a Cython variable to a certain value
-
- cy set my_cython_c_variable = 10
- cy set my_cython_py_variable = $cy_eval("{'doner': 'kebab'}")
-
- This is equivalent to
-
- set $cy_value("my_cython_variable") = 10
- """
-
- name = 'cy set'
- command_class = gdb.COMMAND_DATA
- completer_class = gdb.COMPLETE_NONE
-
- @require_cython_frame
- def invoke(self, expr, from_tty):
- name_and_expr = expr.split('=', 1)
- if len(name_and_expr) != 2:
- raise gdb.GdbError("Invalid expression. Use 'cy set var = expr'.")
-
- varname, expr = name_and_expr
- cname = self.cy.cy_cname.invoke(varname.strip())
- gdb.execute("set %s = %s" % (cname, expr))
-
-
-# Functions
-
-class CyCName(gdb.Function, CythonBase):
- """
- Get the C name of a Cython variable in the current context.
- Examples:
-
- print $cy_cname("function")
- print $cy_cname("Class.method")
- print $cy_cname("module.function")
- """
-
- @require_cython_frame
- @gdb_function_value_to_unicode
- def invoke(self, cyname, frame=None):
- frame = frame or gdb.selected_frame()
- cname = None
-
- if self.is_cython_function(frame):
- cython_function = self.get_cython_function(frame)
- if cyname in cython_function.locals:
- cname = cython_function.locals[cyname].cname
- elif cyname in cython_function.module.globals:
- cname = cython_function.module.globals[cyname].cname
- else:
- qname = '%s.%s' % (cython_function.module.name, cyname)
- if qname in cython_function.module.functions:
- cname = cython_function.module.functions[qname].cname
-
- if not cname:
- cname = self.cy.functions_by_qualified_name.get(cyname)
-
- if not cname:
- raise gdb.GdbError('No such Cython variable: %s' % cyname)
-
- return cname
-
-
-class CyCValue(CyCName):
- """
- Get the value of a Cython variable.
- """
-
- @require_cython_frame
- @gdb_function_value_to_unicode
- def invoke(self, cyname, frame=None):
- globals_dict = self.get_cython_globals_dict()
- cython_function = self.get_cython_function(frame)
-
- if self.is_initialized(cython_function, cyname):
- cname = super(CyCValue, self).invoke(cyname, frame=frame)
- return gdb.parse_and_eval(cname)
- elif cyname in globals_dict:
- return globals_dict[cyname]._gdbval
- else:
- raise gdb.GdbError("Variable %s is not initialized." % cyname)
-
-
-class CyLine(gdb.Function, CythonBase):
- """
- Get the current Cython line.
- """
-
- @require_cython_frame
- def invoke(self):
- return self.get_cython_lineno()
-
-
-class CyEval(gdb.Function, CythonBase, EvaluateOrExecuteCodeMixin):
- """
- Evaluate Python code in the nearest Python or Cython frame and return
- """
-
- @gdb_function_value_to_unicode
- def invoke(self, python_expression):
- input_type = libpython.PythonCodeExecutor.Py_eval_input
- return self.evalcode(python_expression, input_type)
-
-
-cython_info = CythonInfo()
-cy = CyCy.register()
-cython_info.cy = cy
-
-
-def register_defines():
- libpython.source_gdb_script(textwrap.dedent("""\
- define cy step
- cy -step
- end
-
- define cy next
- cy -next
- end
-
- document cy step
- %s
- end
-
- document cy next
- %s
- end
- """) % (CyStep.__doc__, CyNext.__doc__))
-
-register_defines()
+ try:
+ val = gdb.parse_and_eval(cyvar.cname)
+ except RuntimeError:
+ continue
+ else:
+ if val.is_optimized_out:
+ continue
+
+ pystringp = executor.alloc_pystring(name)
+ code = '''
+ (PyObject *) PyDict_SetItem(
+ (PyObject *) %d,
+ (PyObject *) %d,
+ (PyObject *) %s)
+ ''' % (local_dict_pointer, pystringp, cyvar.cname)
+
+ try:
+ if gdb.parse_and_eval(code) < 0:
+ gdb.parse_and_eval('PyErr_Print()')
+ raise gdb.GdbError("Unable to execute Python code.")
+ finally:
+ # PyDict_SetItem doesn't steal our reference
+ executor.xdecref(pystringp)
+
+ def _find_first_cython_or_python_frame(self):
+ frame = gdb.selected_frame()
+ while frame:
+ if (self.is_cython_function(frame) or
+ self.is_python_function(frame)):
+ frame.select()
+ return frame
+
+ frame = frame.older()
+
+ raise gdb.GdbError("There is no Cython or Python frame on the stack.")
+
+ def _evalcode_cython(self, executor, code, input_type):
+ with libpython.FetchAndRestoreError():
+ # get the dict of Cython globals and construct a dict in the
+ # inferior with Cython locals
+ global_dict = gdb.parse_and_eval(
+ '(PyObject *) PyModule_GetDict(__pyx_m)')
+ local_dict = gdb.parse_and_eval('(PyObject *) PyDict_New()')
+
+ try:
+ self._fill_locals_dict(executor,
+ libpython.pointervalue(local_dict))
+ result = executor.evalcode(code, input_type, global_dict,
+ local_dict)
+ finally:
+ executor.xdecref(libpython.pointervalue(local_dict))
+
+ return result
+
+ def evalcode(self, code, input_type):
+ """
+ Evaluate `code` in a Python or Cython stack frame using the given
+ `input_type`.
+ """
+ frame = self._find_first_cython_or_python_frame()
+ executor = libpython.PythonCodeExecutor()
+ if self.is_python_function(frame):
+ return libpython._evalcode_python(executor, code, input_type)
+ return self._evalcode_cython(executor, code, input_type)
+
+
+class CyExec(CythonCommand, libpython.PyExec, EvaluateOrExecuteCodeMixin):
+ """
+ Execute Python code in the nearest Python or Cython frame.
+ """
+
+ name = '-cy-exec'
+ command_class = gdb.COMMAND_STACK
+ completer_class = gdb.COMPLETE_NONE
+
+ def invoke(self, expr, from_tty):
+ expr, input_type = self.readcode(expr)
+ executor = libpython.PythonCodeExecutor()
+ executor.xdecref(self.evalcode(expr, executor.Py_single_input))
+
+
+class CySet(CythonCommand):
+ """
+ Set a Cython variable to a certain value
+
+ cy set my_cython_c_variable = 10
+ cy set my_cython_py_variable = $cy_eval("{'doner': 'kebab'}")
+
+ This is equivalent to
+
+ set $cy_value("my_cython_variable") = 10
+ """
+
+ name = 'cy set'
+ command_class = gdb.COMMAND_DATA
+ completer_class = gdb.COMPLETE_NONE
+
+ @require_cython_frame
+ def invoke(self, expr, from_tty):
+ name_and_expr = expr.split('=', 1)
+ if len(name_and_expr) != 2:
+ raise gdb.GdbError("Invalid expression. Use 'cy set var = expr'.")
+
+ varname, expr = name_and_expr
+ cname = self.cy.cy_cname.invoke(varname.strip())
+ gdb.execute("set %s = %s" % (cname, expr))
+
+
+# Functions
+
+class CyCName(gdb.Function, CythonBase):
+ """
+ Get the C name of a Cython variable in the current context.
+ Examples:
+
+ print $cy_cname("function")
+ print $cy_cname("Class.method")
+ print $cy_cname("module.function")
+ """
+
+ @require_cython_frame
+ @gdb_function_value_to_unicode
+ def invoke(self, cyname, frame=None):
+ frame = frame or gdb.selected_frame()
+ cname = None
+
+ if self.is_cython_function(frame):
+ cython_function = self.get_cython_function(frame)
+ if cyname in cython_function.locals:
+ cname = cython_function.locals[cyname].cname
+ elif cyname in cython_function.module.globals:
+ cname = cython_function.module.globals[cyname].cname
+ else:
+ qname = '%s.%s' % (cython_function.module.name, cyname)
+ if qname in cython_function.module.functions:
+ cname = cython_function.module.functions[qname].cname
+
+ if not cname:
+ cname = self.cy.functions_by_qualified_name.get(cyname)
+
+ if not cname:
+ raise gdb.GdbError('No such Cython variable: %s' % cyname)
+
+ return cname
+
+
+class CyCValue(CyCName):
+ """
+ Get the value of a Cython variable.
+ """
+
+ @require_cython_frame
+ @gdb_function_value_to_unicode
+ def invoke(self, cyname, frame=None):
+ globals_dict = self.get_cython_globals_dict()
+ cython_function = self.get_cython_function(frame)
+
+ if self.is_initialized(cython_function, cyname):
+ cname = super(CyCValue, self).invoke(cyname, frame=frame)
+ return gdb.parse_and_eval(cname)
+ elif cyname in globals_dict:
+ return globals_dict[cyname]._gdbval
+ else:
+ raise gdb.GdbError("Variable %s is not initialized." % cyname)
+
+
+class CyLine(gdb.Function, CythonBase):
+ """
+ Get the current Cython line.
+ """
+
+ @require_cython_frame
+ def invoke(self):
+ return self.get_cython_lineno()
+
+
+class CyEval(gdb.Function, CythonBase, EvaluateOrExecuteCodeMixin):
+ """
+ Evaluate Python code in the nearest Python or Cython frame and return
+ """
+
+ @gdb_function_value_to_unicode
+ def invoke(self, python_expression):
+ input_type = libpython.PythonCodeExecutor.Py_eval_input
+ return self.evalcode(python_expression, input_type)
+
+
+cython_info = CythonInfo()
+cy = CyCy.register()
+cython_info.cy = cy
+
+
+def register_defines():
+ libpython.source_gdb_script(textwrap.dedent("""\
+ define cy step
+ cy -step
+ end
+
+ define cy next
+ cy -next
+ end
+
+ document cy step
+ %s
+ end
+
+ document cy next
+ %s
+ end
+ """) % (CyStep.__doc__, CyNext.__doc__))
+
+register_defines()
diff --git a/contrib/tools/cython/Cython/Debugger/libpython.py b/contrib/tools/cython/Cython/Debugger/libpython.py
index fea626dd73..9285129026 100644
--- a/contrib/tools/cython/Cython/Debugger/libpython.py
+++ b/contrib/tools/cython/Cython/Debugger/libpython.py
@@ -1,73 +1,73 @@
-#!/usr/bin/python
-
-# NOTE: this file is taken from the Python source distribution
-# It can be found under Tools/gdb/libpython.py. It is shipped with Cython
-# because it's not installed as a python module, and because changes are only
-# merged into new python versions (v3.2+).
-
-'''
-From gdb 7 onwards, gdb's build can be configured --with-python, allowing gdb
-to be extended with Python code e.g. for library-specific data visualizations,
-such as for the C++ STL types. Documentation on this API can be seen at:
-http://sourceware.org/gdb/current/onlinedocs/gdb/Python-API.html
-
-
-This python module deals with the case when the process being debugged (the
-"inferior process" in gdb parlance) is itself python, or more specifically,
-linked against libpython. In this situation, almost every item of data is a
-(PyObject*), and having the debugger merely print their addresses is not very
-enlightening.
-
-This module embeds knowledge about the implementation details of libpython so
-that we can emit useful visualizations e.g. a string, a list, a dict, a frame
-giving file/line information and the state of local variables
-
-In particular, given a gdb.Value corresponding to a PyObject* in the inferior
-process, we can generate a "proxy value" within the gdb process. For example,
-given a PyObject* in the inferior process that is in fact a PyListObject*
+#!/usr/bin/python
+
+# NOTE: this file is taken from the Python source distribution
+# It can be found under Tools/gdb/libpython.py. It is shipped with Cython
+# because it's not installed as a python module, and because changes are only
+# merged into new python versions (v3.2+).
+
+'''
+From gdb 7 onwards, gdb's build can be configured --with-python, allowing gdb
+to be extended with Python code e.g. for library-specific data visualizations,
+such as for the C++ STL types. Documentation on this API can be seen at:
+http://sourceware.org/gdb/current/onlinedocs/gdb/Python-API.html
+
+
+This python module deals with the case when the process being debugged (the
+"inferior process" in gdb parlance) is itself python, or more specifically,
+linked against libpython. In this situation, almost every item of data is a
+(PyObject*), and having the debugger merely print their addresses is not very
+enlightening.
+
+This module embeds knowledge about the implementation details of libpython so
+that we can emit useful visualizations e.g. a string, a list, a dict, a frame
+giving file/line information and the state of local variables
+
+In particular, given a gdb.Value corresponding to a PyObject* in the inferior
+process, we can generate a "proxy value" within the gdb process. For example,
+given a PyObject* in the inferior process that is in fact a PyListObject*
holding three PyObject* that turn out to be PyBytesObject* instances, we can
generate a proxy value within the gdb process that is a list of bytes
instances:
[b"foo", b"bar", b"baz"]
-
-Doing so can be expensive for complicated graphs of objects, and could take
-some time, so we also have a "write_repr" method that writes a representation
-of the data to a file-like object. This allows us to stop the traversal by
-having the file-like object raise an exception if it gets too much data.
-
-With both "proxyval" and "write_repr" we keep track of the set of all addresses
-visited so far in the traversal, to avoid infinite recursion due to cycles in
-the graph of object references.
-
-We try to defer gdb.lookup_type() invocations for python types until as late as
-possible: for a dynamically linked python binary, when the process starts in
-the debugger, the libpython.so hasn't been dynamically loaded yet, so none of
-the type names are known to the debugger
-
-The module also extends gdb with some python-specific commands.
-'''
+
+Doing so can be expensive for complicated graphs of objects, and could take
+some time, so we also have a "write_repr" method that writes a representation
+of the data to a file-like object. This allows us to stop the traversal by
+having the file-like object raise an exception if it gets too much data.
+
+With both "proxyval" and "write_repr" we keep track of the set of all addresses
+visited so far in the traversal, to avoid infinite recursion due to cycles in
+the graph of object references.
+
+We try to defer gdb.lookup_type() invocations for python types until as late as
+possible: for a dynamically linked python binary, when the process starts in
+the debugger, the libpython.so hasn't been dynamically loaded yet, so none of
+the type names are known to the debugger
+
+The module also extends gdb with some python-specific commands.
+'''
# NOTE: some gdbs are linked with Python 3, so this file should be dual-syntax
# compatible (2.6+ and 3.0+). See #19308.
from __future__ import print_function
import gdb
-import os
+import os
import locale
-import sys
-
+import sys
+
if sys.version_info[0] >= 3:
unichr = chr
- xrange = range
+ xrange = range
long = int
-
-# Look up the gdb.Type for some standard types:
+
+# Look up the gdb.Type for some standard types:
# Those need to be refreshed as types (pointer sizes) may change when
# gdb loads different executables
-
+
def _type_char_ptr():
return gdb.lookup_type('char').pointer() # char*
-
+
def _type_unsigned_char_ptr():
return gdb.lookup_type('unsigned char').pointer() # unsigned char*
@@ -88,41 +88,41 @@ def _sizeof_void_p():
# value computed later, see PyUnicodeObjectPtr.proxy()
_is_pep393 = None
-Py_TPFLAGS_HEAPTYPE = (1 << 9)
-Py_TPFLAGS_LONG_SUBCLASS = (1 << 24)
-Py_TPFLAGS_LIST_SUBCLASS = (1 << 25)
-Py_TPFLAGS_TUPLE_SUBCLASS = (1 << 26)
-Py_TPFLAGS_BYTES_SUBCLASS = (1 << 27)
-Py_TPFLAGS_UNICODE_SUBCLASS = (1 << 28)
-Py_TPFLAGS_DICT_SUBCLASS = (1 << 29)
-Py_TPFLAGS_BASE_EXC_SUBCLASS = (1 << 30)
-Py_TPFLAGS_TYPE_SUBCLASS = (1 << 31)
-
-
+Py_TPFLAGS_HEAPTYPE = (1 << 9)
+Py_TPFLAGS_LONG_SUBCLASS = (1 << 24)
+Py_TPFLAGS_LIST_SUBCLASS = (1 << 25)
+Py_TPFLAGS_TUPLE_SUBCLASS = (1 << 26)
+Py_TPFLAGS_BYTES_SUBCLASS = (1 << 27)
+Py_TPFLAGS_UNICODE_SUBCLASS = (1 << 28)
+Py_TPFLAGS_DICT_SUBCLASS = (1 << 29)
+Py_TPFLAGS_BASE_EXC_SUBCLASS = (1 << 30)
+Py_TPFLAGS_TYPE_SUBCLASS = (1 << 31)
+
+
MAX_OUTPUT_LEN=1024
-hexdigits = "0123456789abcdef"
-
-ENCODING = locale.getpreferredencoding()
-
+hexdigits = "0123456789abcdef"
+
+ENCODING = locale.getpreferredencoding()
+
EVALFRAME = '_PyEval_EvalFrameDefault'
-
-class NullPyObjectPtr(RuntimeError):
- pass
-
-
-def safety_limit(val):
+
+class NullPyObjectPtr(RuntimeError):
+ pass
+
+
+def safety_limit(val):
# Given an integer value from the process being debugged, limit it to some
- # safety threshold so that arbitrary breakage within said process doesn't
- # break the gdb process too much (e.g. sizes of iterations, sizes of lists)
- return min(val, 1000)
-
-
-def safe_range(val):
- # As per range, but don't trust the value too much: cap it to a safety
- # threshold in case the data was corrupted
+ # safety threshold so that arbitrary breakage within said process doesn't
+ # break the gdb process too much (e.g. sizes of iterations, sizes of lists)
+ return min(val, 1000)
+
+
+def safe_range(val):
+ # As per range, but don't trust the value too much: cap it to a safety
+ # threshold in case the data was corrupted
return xrange(safety_limit(int(val)))
-
+
if sys.version_info[0] >= 3:
def write_unicode(file, text):
file.write(text)
@@ -134,7 +134,7 @@ else:
if isinstance(text, unicode):
text = text.encode(ENCODING, 'backslashreplace')
file.write(text)
-
+
try:
os_fsencode = os.fsencode
except AttributeError:
@@ -154,525 +154,525 @@ except AttributeError:
byte = char.encode(encoding)
encoded.append(byte)
return ''.join(encoded)
-
-class StringTruncated(RuntimeError):
- pass
-
-class TruncatedStringIO(object):
+
+class StringTruncated(RuntimeError):
+ pass
+
+class TruncatedStringIO(object):
'''Similar to io.StringIO, but can truncate the output by raising a
- StringTruncated exception'''
- def __init__(self, maxlen=None):
- self._val = ''
- self.maxlen = maxlen
-
- def write(self, data):
- if self.maxlen:
- if len(data) + len(self._val) > self.maxlen:
- # Truncation:
- self._val += data[0:self.maxlen - len(self._val)]
- raise StringTruncated()
-
- self._val += data
-
- def getvalue(self):
- return self._val
-
-class PyObjectPtr(object):
- """
+ StringTruncated exception'''
+ def __init__(self, maxlen=None):
+ self._val = ''
+ self.maxlen = maxlen
+
+ def write(self, data):
+ if self.maxlen:
+ if len(data) + len(self._val) > self.maxlen:
+ # Truncation:
+ self._val += data[0:self.maxlen - len(self._val)]
+ raise StringTruncated()
+
+ self._val += data
+
+ def getvalue(self):
+ return self._val
+
+class PyObjectPtr(object):
+ """
Class wrapping a gdb.Value that's either a (PyObject*) within the
inferior process, or some subclass pointer e.g. (PyBytesObject*)
-
- There will be a subclass for every refined PyObject type that we care
- about.
-
- Note that at every stage the underlying pointer could be NULL, point
- to corrupt data, etc; this is the debugger, after all.
- """
- _typename = 'PyObject'
-
- def __init__(self, gdbval, cast_to=None):
- if cast_to:
- self._gdbval = gdbval.cast(cast_to)
- else:
- self._gdbval = gdbval
-
- def field(self, name):
- '''
- Get the gdb.Value for the given field within the PyObject, coping with
- some python 2 versus python 3 differences.
-
- Various libpython types are defined using the "PyObject_HEAD" and
- "PyObject_VAR_HEAD" macros.
-
- In Python 2, this these are defined so that "ob_type" and (for a var
- object) "ob_size" are fields of the type in question.
-
- In Python 3, this is defined as an embedded PyVarObject type thus:
- PyVarObject ob_base;
- so that the "ob_size" field is located insize the "ob_base" field, and
- the "ob_type" is most easily accessed by casting back to a (PyObject*).
- '''
- if self.is_null():
- raise NullPyObjectPtr(self)
-
- if name == 'ob_type':
- pyo_ptr = self._gdbval.cast(PyObjectPtr.get_gdb_type())
- return pyo_ptr.dereference()[name]
-
- if name == 'ob_size':
- pyo_ptr = self._gdbval.cast(PyVarObjectPtr.get_gdb_type())
- return pyo_ptr.dereference()[name]
-
- # General case: look it up inside the object:
- return self._gdbval.dereference()[name]
-
- def pyop_field(self, name):
- '''
- Get a PyObjectPtr for the given PyObject* field within this PyObject,
- coping with some python 2 versus python 3 differences.
- '''
- return PyObjectPtr.from_pyobject_ptr(self.field(name))
-
- def write_field_repr(self, name, out, visited):
- '''
- Extract the PyObject* field named "name", and write its representation
- to file-like object "out"
- '''
- field_obj = self.pyop_field(name)
- field_obj.write_repr(out, visited)
-
- def get_truncated_repr(self, maxlen):
- '''
- Get a repr-like string for the data, but truncate it at "maxlen" bytes
- (ending the object graph traversal as soon as you do)
- '''
- out = TruncatedStringIO(maxlen)
- try:
- self.write_repr(out, set())
- except StringTruncated:
- # Truncation occurred:
- return out.getvalue() + '...(truncated)'
-
- # No truncation occurred:
- return out.getvalue()
-
- def type(self):
- return PyTypeObjectPtr(self.field('ob_type'))
-
- def is_null(self):
+
+ There will be a subclass for every refined PyObject type that we care
+ about.
+
+ Note that at every stage the underlying pointer could be NULL, point
+ to corrupt data, etc; this is the debugger, after all.
+ """
+ _typename = 'PyObject'
+
+ def __init__(self, gdbval, cast_to=None):
+ if cast_to:
+ self._gdbval = gdbval.cast(cast_to)
+ else:
+ self._gdbval = gdbval
+
+ def field(self, name):
+ '''
+ Get the gdb.Value for the given field within the PyObject, coping with
+ some python 2 versus python 3 differences.
+
+ Various libpython types are defined using the "PyObject_HEAD" and
+ "PyObject_VAR_HEAD" macros.
+
+ In Python 2, this these are defined so that "ob_type" and (for a var
+ object) "ob_size" are fields of the type in question.
+
+ In Python 3, this is defined as an embedded PyVarObject type thus:
+ PyVarObject ob_base;
+ so that the "ob_size" field is located insize the "ob_base" field, and
+ the "ob_type" is most easily accessed by casting back to a (PyObject*).
+ '''
+ if self.is_null():
+ raise NullPyObjectPtr(self)
+
+ if name == 'ob_type':
+ pyo_ptr = self._gdbval.cast(PyObjectPtr.get_gdb_type())
+ return pyo_ptr.dereference()[name]
+
+ if name == 'ob_size':
+ pyo_ptr = self._gdbval.cast(PyVarObjectPtr.get_gdb_type())
+ return pyo_ptr.dereference()[name]
+
+ # General case: look it up inside the object:
+ return self._gdbval.dereference()[name]
+
+ def pyop_field(self, name):
+ '''
+ Get a PyObjectPtr for the given PyObject* field within this PyObject,
+ coping with some python 2 versus python 3 differences.
+ '''
+ return PyObjectPtr.from_pyobject_ptr(self.field(name))
+
+ def write_field_repr(self, name, out, visited):
+ '''
+ Extract the PyObject* field named "name", and write its representation
+ to file-like object "out"
+ '''
+ field_obj = self.pyop_field(name)
+ field_obj.write_repr(out, visited)
+
+ def get_truncated_repr(self, maxlen):
+ '''
+ Get a repr-like string for the data, but truncate it at "maxlen" bytes
+ (ending the object graph traversal as soon as you do)
+ '''
+ out = TruncatedStringIO(maxlen)
+ try:
+ self.write_repr(out, set())
+ except StringTruncated:
+ # Truncation occurred:
+ return out.getvalue() + '...(truncated)'
+
+ # No truncation occurred:
+ return out.getvalue()
+
+ def type(self):
+ return PyTypeObjectPtr(self.field('ob_type'))
+
+ def is_null(self):
return 0 == long(self._gdbval)
-
- def is_optimized_out(self):
- '''
- Is the value of the underlying PyObject* visible to the debugger?
-
- This can vary with the precise version of the compiler used to build
- Python, and the precise version of gdb.
-
- See e.g. https://bugzilla.redhat.com/show_bug.cgi?id=556975 with
- PyEval_EvalFrameEx's "f"
- '''
- return self._gdbval.is_optimized_out
-
- def safe_tp_name(self):
- try:
- return self.type().field('tp_name').string()
- except NullPyObjectPtr:
- # NULL tp_name?
- return 'unknown'
- except RuntimeError:
- # Can't even read the object at all?
- return 'unknown'
-
- def proxyval(self, visited):
- '''
- Scrape a value from the inferior process, and try to represent it
- within the gdb process, whilst (hopefully) avoiding crashes when
- the remote data is corrupt.
-
- Derived classes will override this.
-
- For example, a PyIntObject* with ob_ival 42 in the inferior process
- should result in an int(42) in this process.
-
- visited: a set of all gdb.Value pyobject pointers already visited
- whilst generating this value (to guard against infinite recursion when
- visiting object graphs with loops). Analogous to Py_ReprEnter and
- Py_ReprLeave
- '''
-
- class FakeRepr(object):
- """
- Class representing a non-descript PyObject* value in the inferior
- process for when we don't have a custom scraper, intended to have
- a sane repr().
- """
-
- def __init__(self, tp_name, address):
- self.tp_name = tp_name
- self.address = address
-
- def __repr__(self):
- # For the NULL pointer, we have no way of knowing a type, so
- # special-case it as per
- # http://bugs.python.org/issue8032#msg100882
- if self.address == 0:
- return '0x0'
- return '<%s at remote 0x%x>' % (self.tp_name, self.address)
-
- return FakeRepr(self.safe_tp_name(),
+
+ def is_optimized_out(self):
+ '''
+ Is the value of the underlying PyObject* visible to the debugger?
+
+ This can vary with the precise version of the compiler used to build
+ Python, and the precise version of gdb.
+
+ See e.g. https://bugzilla.redhat.com/show_bug.cgi?id=556975 with
+ PyEval_EvalFrameEx's "f"
+ '''
+ return self._gdbval.is_optimized_out
+
+ def safe_tp_name(self):
+ try:
+ return self.type().field('tp_name').string()
+ except NullPyObjectPtr:
+ # NULL tp_name?
+ return 'unknown'
+ except RuntimeError:
+ # Can't even read the object at all?
+ return 'unknown'
+
+ def proxyval(self, visited):
+ '''
+ Scrape a value from the inferior process, and try to represent it
+ within the gdb process, whilst (hopefully) avoiding crashes when
+ the remote data is corrupt.
+
+ Derived classes will override this.
+
+ For example, a PyIntObject* with ob_ival 42 in the inferior process
+ should result in an int(42) in this process.
+
+ visited: a set of all gdb.Value pyobject pointers already visited
+ whilst generating this value (to guard against infinite recursion when
+ visiting object graphs with loops). Analogous to Py_ReprEnter and
+ Py_ReprLeave
+ '''
+
+ class FakeRepr(object):
+ """
+ Class representing a non-descript PyObject* value in the inferior
+ process for when we don't have a custom scraper, intended to have
+ a sane repr().
+ """
+
+ def __init__(self, tp_name, address):
+ self.tp_name = tp_name
+ self.address = address
+
+ def __repr__(self):
+ # For the NULL pointer, we have no way of knowing a type, so
+ # special-case it as per
+ # http://bugs.python.org/issue8032#msg100882
+ if self.address == 0:
+ return '0x0'
+ return '<%s at remote 0x%x>' % (self.tp_name, self.address)
+
+ return FakeRepr(self.safe_tp_name(),
long(self._gdbval))
-
- def write_repr(self, out, visited):
- '''
- Write a string representation of the value scraped from the inferior
- process to "out", a file-like object.
- '''
- # Default implementation: generate a proxy value and write its repr
- # However, this could involve a lot of work for complicated objects,
- # so for derived classes we specialize this
- return out.write(repr(self.proxyval(visited)))
-
- @classmethod
- def subclass_from_type(cls, t):
- '''
- Given a PyTypeObjectPtr instance wrapping a gdb.Value that's a
- (PyTypeObject*), determine the corresponding subclass of PyObjectPtr
- to use
-
- Ideally, we would look up the symbols for the global types, but that
- isn't working yet:
- (gdb) python print gdb.lookup_symbol('PyList_Type')[0].value
- Traceback (most recent call last):
- File "<string>", line 1, in <module>
- NotImplementedError: Symbol type not yet supported in Python scripts.
- Error while executing Python code.
-
- For now, we use tp_flags, after doing some string comparisons on the
- tp_name for some special-cases that don't seem to be visible through
- flags
- '''
- try:
- tp_name = t.field('tp_name').string()
- tp_flags = int(t.field('tp_flags'))
- except RuntimeError:
- # Handle any kind of error e.g. NULL ptrs by simply using the base
- # class
- return cls
-
+
+ def write_repr(self, out, visited):
+ '''
+ Write a string representation of the value scraped from the inferior
+ process to "out", a file-like object.
+ '''
+ # Default implementation: generate a proxy value and write its repr
+ # However, this could involve a lot of work for complicated objects,
+ # so for derived classes we specialize this
+ return out.write(repr(self.proxyval(visited)))
+
+ @classmethod
+ def subclass_from_type(cls, t):
+ '''
+ Given a PyTypeObjectPtr instance wrapping a gdb.Value that's a
+ (PyTypeObject*), determine the corresponding subclass of PyObjectPtr
+ to use
+
+ Ideally, we would look up the symbols for the global types, but that
+ isn't working yet:
+ (gdb) python print gdb.lookup_symbol('PyList_Type')[0].value
+ Traceback (most recent call last):
+ File "<string>", line 1, in <module>
+ NotImplementedError: Symbol type not yet supported in Python scripts.
+ Error while executing Python code.
+
+ For now, we use tp_flags, after doing some string comparisons on the
+ tp_name for some special-cases that don't seem to be visible through
+ flags
+ '''
+ try:
+ tp_name = t.field('tp_name').string()
+ tp_flags = int(t.field('tp_flags'))
+ except RuntimeError:
+ # Handle any kind of error e.g. NULL ptrs by simply using the base
+ # class
+ return cls
+
#print('tp_flags = 0x%08x' % tp_flags)
#print('tp_name = %r' % tp_name)
-
- name_map = {'bool': PyBoolObjectPtr,
- 'classobj': PyClassObjectPtr,
- 'NoneType': PyNoneStructPtr,
- 'frame': PyFrameObjectPtr,
- 'set' : PySetObjectPtr,
- 'frozenset' : PySetObjectPtr,
- 'builtin_function_or_method' : PyCFunctionObjectPtr,
+
+ name_map = {'bool': PyBoolObjectPtr,
+ 'classobj': PyClassObjectPtr,
+ 'NoneType': PyNoneStructPtr,
+ 'frame': PyFrameObjectPtr,
+ 'set' : PySetObjectPtr,
+ 'frozenset' : PySetObjectPtr,
+ 'builtin_function_or_method' : PyCFunctionObjectPtr,
'method-wrapper': wrapperobject,
- }
- if tp_name in name_map:
- return name_map[tp_name]
-
+ }
+ if tp_name in name_map:
+ return name_map[tp_name]
+
if tp_flags & Py_TPFLAGS_HEAPTYPE:
return HeapTypeObjectPtr
-
- if tp_flags & Py_TPFLAGS_LONG_SUBCLASS:
- return PyLongObjectPtr
- if tp_flags & Py_TPFLAGS_LIST_SUBCLASS:
- return PyListObjectPtr
- if tp_flags & Py_TPFLAGS_TUPLE_SUBCLASS:
- return PyTupleObjectPtr
+
+ if tp_flags & Py_TPFLAGS_LONG_SUBCLASS:
+ return PyLongObjectPtr
+ if tp_flags & Py_TPFLAGS_LIST_SUBCLASS:
+ return PyListObjectPtr
+ if tp_flags & Py_TPFLAGS_TUPLE_SUBCLASS:
+ return PyTupleObjectPtr
if tp_flags & Py_TPFLAGS_BYTES_SUBCLASS:
return PyBytesObjectPtr
- if tp_flags & Py_TPFLAGS_UNICODE_SUBCLASS:
- return PyUnicodeObjectPtr
- if tp_flags & Py_TPFLAGS_DICT_SUBCLASS:
- return PyDictObjectPtr
- if tp_flags & Py_TPFLAGS_BASE_EXC_SUBCLASS:
- return PyBaseExceptionObjectPtr
+ if tp_flags & Py_TPFLAGS_UNICODE_SUBCLASS:
+ return PyUnicodeObjectPtr
+ if tp_flags & Py_TPFLAGS_DICT_SUBCLASS:
+ return PyDictObjectPtr
+ if tp_flags & Py_TPFLAGS_BASE_EXC_SUBCLASS:
+ return PyBaseExceptionObjectPtr
#if tp_flags & Py_TPFLAGS_TYPE_SUBCLASS:
# return PyTypeObjectPtr
-
- # Use the base class:
- return cls
-
- @classmethod
- def from_pyobject_ptr(cls, gdbval):
- '''
- Try to locate the appropriate derived class dynamically, and cast
- the pointer accordingly.
- '''
- try:
- p = PyObjectPtr(gdbval)
- cls = cls.subclass_from_type(p.type())
- return cls(gdbval, cast_to=cls.get_gdb_type())
+
+ # Use the base class:
+ return cls
+
+ @classmethod
+ def from_pyobject_ptr(cls, gdbval):
+ '''
+ Try to locate the appropriate derived class dynamically, and cast
+ the pointer accordingly.
+ '''
+ try:
+ p = PyObjectPtr(gdbval)
+ cls = cls.subclass_from_type(p.type())
+ return cls(gdbval, cast_to=cls.get_gdb_type())
except RuntimeError:
- # Handle any kind of error e.g. NULL ptrs by simply using the base
- # class
- pass
- return cls(gdbval)
-
- @classmethod
- def get_gdb_type(cls):
- return gdb.lookup_type(cls._typename).pointer()
-
- def as_address(self):
+ # Handle any kind of error e.g. NULL ptrs by simply using the base
+ # class
+ pass
+ return cls(gdbval)
+
+ @classmethod
+ def get_gdb_type(cls):
+ return gdb.lookup_type(cls._typename).pointer()
+
+ def as_address(self):
return long(self._gdbval)
-
-class PyVarObjectPtr(PyObjectPtr):
- _typename = 'PyVarObject'
-
-class ProxyAlreadyVisited(object):
- '''
- Placeholder proxy to use when protecting against infinite recursion due to
- loops in the object graph.
-
- Analogous to the values emitted by the users of Py_ReprEnter and Py_ReprLeave
- '''
- def __init__(self, rep):
- self._rep = rep
-
- def __repr__(self):
- return self._rep
-
-
-def _write_instance_repr(out, visited, name, pyop_attrdict, address):
+
+class PyVarObjectPtr(PyObjectPtr):
+ _typename = 'PyVarObject'
+
+class ProxyAlreadyVisited(object):
+ '''
+ Placeholder proxy to use when protecting against infinite recursion due to
+ loops in the object graph.
+
+ Analogous to the values emitted by the users of Py_ReprEnter and Py_ReprLeave
+ '''
+ def __init__(self, rep):
+ self._rep = rep
+
+ def __repr__(self):
+ return self._rep
+
+
+def _write_instance_repr(out, visited, name, pyop_attrdict, address):
'''Shared code for use by all classes:
- write a representation to file-like object "out"'''
- out.write('<')
- out.write(name)
-
- # Write dictionary of instance attributes:
- if isinstance(pyop_attrdict, PyDictObjectPtr):
- out.write('(')
- first = True
+ write a representation to file-like object "out"'''
+ out.write('<')
+ out.write(name)
+
+ # Write dictionary of instance attributes:
+ if isinstance(pyop_attrdict, PyDictObjectPtr):
+ out.write('(')
+ first = True
for pyop_arg, pyop_val in pyop_attrdict.iteritems():
- if not first:
- out.write(', ')
- first = False
- out.write(pyop_arg.proxyval(visited))
- out.write('=')
- pyop_val.write_repr(out, visited)
- out.write(')')
- out.write(' at remote 0x%x>' % address)
-
-
-class InstanceProxy(object):
-
- def __init__(self, cl_name, attrdict, address):
- self.cl_name = cl_name
- self.attrdict = attrdict
- self.address = address
-
- def __repr__(self):
- if isinstance(self.attrdict, dict):
+ if not first:
+ out.write(', ')
+ first = False
+ out.write(pyop_arg.proxyval(visited))
+ out.write('=')
+ pyop_val.write_repr(out, visited)
+ out.write(')')
+ out.write(' at remote 0x%x>' % address)
+
+
+class InstanceProxy(object):
+
+ def __init__(self, cl_name, attrdict, address):
+ self.cl_name = cl_name
+ self.attrdict = attrdict
+ self.address = address
+
+ def __repr__(self):
+ if isinstance(self.attrdict, dict):
kwargs = ', '.join(["%s=%r" % (arg, val)
for arg, val in self.attrdict.iteritems()])
return '<%s(%s) at remote 0x%x>' % (self.cl_name,
kwargs, self.address)
- else:
+ else:
return '<%s at remote 0x%x>' % (self.cl_name,
self.address)
-
+
def _PyObject_VAR_SIZE(typeobj, nitems):
if _PyObject_VAR_SIZE._type_size_t is None:
_PyObject_VAR_SIZE._type_size_t = gdb.lookup_type('size_t')
-
- return ( ( typeobj.field('tp_basicsize') +
- nitems * typeobj.field('tp_itemsize') +
+
+ return ( ( typeobj.field('tp_basicsize') +
+ nitems * typeobj.field('tp_itemsize') +
(_sizeof_void_p() - 1)
) & ~(_sizeof_void_p() - 1)
).cast(_PyObject_VAR_SIZE._type_size_t)
_PyObject_VAR_SIZE._type_size_t = None
-
+
class HeapTypeObjectPtr(PyObjectPtr):
_typename = 'PyObject'
-
- def get_attr_dict(self):
- '''
- Get the PyDictObject ptr representing the attribute dictionary
- (or None if there's a problem)
- '''
- try:
- typeobj = self.type()
- dictoffset = int_from_int(typeobj.field('tp_dictoffset'))
- if dictoffset != 0:
- if dictoffset < 0:
- type_PyVarObject_ptr = gdb.lookup_type('PyVarObject').pointer()
- tsize = int_from_int(self._gdbval.cast(type_PyVarObject_ptr)['ob_size'])
- if tsize < 0:
- tsize = -tsize
- size = _PyObject_VAR_SIZE(typeobj, tsize)
- dictoffset += size
- assert dictoffset > 0
+
+ def get_attr_dict(self):
+ '''
+ Get the PyDictObject ptr representing the attribute dictionary
+ (or None if there's a problem)
+ '''
+ try:
+ typeobj = self.type()
+ dictoffset = int_from_int(typeobj.field('tp_dictoffset'))
+ if dictoffset != 0:
+ if dictoffset < 0:
+ type_PyVarObject_ptr = gdb.lookup_type('PyVarObject').pointer()
+ tsize = int_from_int(self._gdbval.cast(type_PyVarObject_ptr)['ob_size'])
+ if tsize < 0:
+ tsize = -tsize
+ size = _PyObject_VAR_SIZE(typeobj, tsize)
+ dictoffset += size
+ assert dictoffset > 0
assert dictoffset % _sizeof_void_p() == 0
-
+
dictptr = self._gdbval.cast(_type_char_ptr()) + dictoffset
- PyObjectPtrPtr = PyObjectPtr.get_gdb_type().pointer()
- dictptr = dictptr.cast(PyObjectPtrPtr)
- return PyObjectPtr.from_pyobject_ptr(dictptr.dereference())
- except RuntimeError:
- # Corrupt data somewhere; fail safe
- pass
-
- # Not found, or some kind of error:
- return None
-
- def proxyval(self, visited):
- '''
+ PyObjectPtrPtr = PyObjectPtr.get_gdb_type().pointer()
+ dictptr = dictptr.cast(PyObjectPtrPtr)
+ return PyObjectPtr.from_pyobject_ptr(dictptr.dereference())
+ except RuntimeError:
+ # Corrupt data somewhere; fail safe
+ pass
+
+ # Not found, or some kind of error:
+ return None
+
+ def proxyval(self, visited):
+ '''
Support for classes.
-
- Currently we just locate the dictionary using a transliteration to
- python of _PyObject_GetDictPtr, ignoring descriptors
- '''
- # Guard against infinite loops:
- if self.as_address() in visited:
- return ProxyAlreadyVisited('<...>')
- visited.add(self.as_address())
-
- pyop_attr_dict = self.get_attr_dict()
- if pyop_attr_dict:
- attr_dict = pyop_attr_dict.proxyval(visited)
- else:
- attr_dict = {}
- tp_name = self.safe_tp_name()
-
+
+ Currently we just locate the dictionary using a transliteration to
+ python of _PyObject_GetDictPtr, ignoring descriptors
+ '''
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ return ProxyAlreadyVisited('<...>')
+ visited.add(self.as_address())
+
+ pyop_attr_dict = self.get_attr_dict()
+ if pyop_attr_dict:
+ attr_dict = pyop_attr_dict.proxyval(visited)
+ else:
+ attr_dict = {}
+ tp_name = self.safe_tp_name()
+
# Class:
return InstanceProxy(tp_name, attr_dict, long(self._gdbval))
-
- def write_repr(self, out, visited):
- # Guard against infinite loops:
- if self.as_address() in visited:
- out.write('<...>')
- return
- visited.add(self.as_address())
-
+
+ def write_repr(self, out, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ out.write('<...>')
+ return
+ visited.add(self.as_address())
+
pyop_attrdict = self.get_attr_dict()
_write_instance_repr(out, visited,
self.safe_tp_name(), pyop_attrdict, self.as_address())
-
-class ProxyException(Exception):
- def __init__(self, tp_name, args):
- self.tp_name = tp_name
- self.args = args
-
- def __repr__(self):
- return '%s%r' % (self.tp_name, self.args)
-
-class PyBaseExceptionObjectPtr(PyObjectPtr):
- """
- Class wrapping a gdb.Value that's a PyBaseExceptionObject* i.e. an exception
- within the process being debugged.
- """
- _typename = 'PyBaseExceptionObject'
-
- def proxyval(self, visited):
- # Guard against infinite loops:
- if self.as_address() in visited:
- return ProxyAlreadyVisited('(...)')
- visited.add(self.as_address())
- arg_proxy = self.pyop_field('args').proxyval(visited)
- return ProxyException(self.safe_tp_name(),
- arg_proxy)
-
- def write_repr(self, out, visited):
- # Guard against infinite loops:
- if self.as_address() in visited:
- out.write('(...)')
- return
- visited.add(self.as_address())
-
- out.write(self.safe_tp_name())
- self.write_field_repr('args', out, visited)
-
-class PyClassObjectPtr(PyObjectPtr):
- """
- Class wrapping a gdb.Value that's a PyClassObject* i.e. a <classobj>
- instance within the process being debugged.
- """
- _typename = 'PyClassObject'
-
-
-class BuiltInFunctionProxy(object):
- def __init__(self, ml_name):
- self.ml_name = ml_name
-
- def __repr__(self):
- return "<built-in function %s>" % self.ml_name
-
-class BuiltInMethodProxy(object):
- def __init__(self, ml_name, pyop_m_self):
- self.ml_name = ml_name
- self.pyop_m_self = pyop_m_self
-
- def __repr__(self):
+
+class ProxyException(Exception):
+ def __init__(self, tp_name, args):
+ self.tp_name = tp_name
+ self.args = args
+
+ def __repr__(self):
+ return '%s%r' % (self.tp_name, self.args)
+
+class PyBaseExceptionObjectPtr(PyObjectPtr):
+ """
+ Class wrapping a gdb.Value that's a PyBaseExceptionObject* i.e. an exception
+ within the process being debugged.
+ """
+ _typename = 'PyBaseExceptionObject'
+
+ def proxyval(self, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ return ProxyAlreadyVisited('(...)')
+ visited.add(self.as_address())
+ arg_proxy = self.pyop_field('args').proxyval(visited)
+ return ProxyException(self.safe_tp_name(),
+ arg_proxy)
+
+ def write_repr(self, out, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ out.write('(...)')
+ return
+ visited.add(self.as_address())
+
+ out.write(self.safe_tp_name())
+ self.write_field_repr('args', out, visited)
+
+class PyClassObjectPtr(PyObjectPtr):
+ """
+ Class wrapping a gdb.Value that's a PyClassObject* i.e. a <classobj>
+ instance within the process being debugged.
+ """
+ _typename = 'PyClassObject'
+
+
+class BuiltInFunctionProxy(object):
+ def __init__(self, ml_name):
+ self.ml_name = ml_name
+
+ def __repr__(self):
+ return "<built-in function %s>" % self.ml_name
+
+class BuiltInMethodProxy(object):
+ def __init__(self, ml_name, pyop_m_self):
+ self.ml_name = ml_name
+ self.pyop_m_self = pyop_m_self
+
+ def __repr__(self):
return ('<built-in method %s of %s object at remote 0x%x>'
% (self.ml_name,
self.pyop_m_self.safe_tp_name(),
self.pyop_m_self.as_address())
)
-
-class PyCFunctionObjectPtr(PyObjectPtr):
- """
- Class wrapping a gdb.Value that's a PyCFunctionObject*
- (see Include/methodobject.h and Objects/methodobject.c)
- """
- _typename = 'PyCFunctionObject'
-
- def proxyval(self, visited):
- m_ml = self.field('m_ml') # m_ml is a (PyMethodDef*)
- ml_name = m_ml['ml_name'].string()
-
- pyop_m_self = self.pyop_field('m_self')
- if pyop_m_self.is_null():
- return BuiltInFunctionProxy(ml_name)
- else:
- return BuiltInMethodProxy(ml_name, pyop_m_self)
-
-
-class PyCodeObjectPtr(PyObjectPtr):
- """
- Class wrapping a gdb.Value that's a PyCodeObject* i.e. a <code> instance
- within the process being debugged.
- """
- _typename = 'PyCodeObject'
-
- def addr2line(self, addrq):
- '''
- Get the line number for a given bytecode offset
-
- Analogous to PyCode_Addr2Line; translated from pseudocode in
- Objects/lnotab_notes.txt
- '''
- co_lnotab = self.pyop_field('co_lnotab').proxyval(set())
-
- # Initialize lineno to co_firstlineno as per PyCode_Addr2Line
- # not 0, as lnotab_notes.txt has it:
- lineno = int_from_int(self.field('co_firstlineno'))
-
- addr = 0
- for addr_incr, line_incr in zip(co_lnotab[::2], co_lnotab[1::2]):
- addr += ord(addr_incr)
- if addr > addrq:
- return lineno
- lineno += ord(line_incr)
- return lineno
-
-
-class PyDictObjectPtr(PyObjectPtr):
- """
- Class wrapping a gdb.Value that's a PyDictObject* i.e. a dict instance
- within the process being debugged.
- """
- _typename = 'PyDictObject'
-
- def iteritems(self):
- '''
- Yields a sequence of (PyObjectPtr key, PyObjectPtr value) pairs,
+
+class PyCFunctionObjectPtr(PyObjectPtr):
+ """
+ Class wrapping a gdb.Value that's a PyCFunctionObject*
+ (see Include/methodobject.h and Objects/methodobject.c)
+ """
+ _typename = 'PyCFunctionObject'
+
+ def proxyval(self, visited):
+ m_ml = self.field('m_ml') # m_ml is a (PyMethodDef*)
+ ml_name = m_ml['ml_name'].string()
+
+ pyop_m_self = self.pyop_field('m_self')
+ if pyop_m_self.is_null():
+ return BuiltInFunctionProxy(ml_name)
+ else:
+ return BuiltInMethodProxy(ml_name, pyop_m_self)
+
+
+class PyCodeObjectPtr(PyObjectPtr):
+ """
+ Class wrapping a gdb.Value that's a PyCodeObject* i.e. a <code> instance
+ within the process being debugged.
+ """
+ _typename = 'PyCodeObject'
+
+ def addr2line(self, addrq):
+ '''
+ Get the line number for a given bytecode offset
+
+ Analogous to PyCode_Addr2Line; translated from pseudocode in
+ Objects/lnotab_notes.txt
+ '''
+ co_lnotab = self.pyop_field('co_lnotab').proxyval(set())
+
+ # Initialize lineno to co_firstlineno as per PyCode_Addr2Line
+ # not 0, as lnotab_notes.txt has it:
+ lineno = int_from_int(self.field('co_firstlineno'))
+
+ addr = 0
+ for addr_incr, line_incr in zip(co_lnotab[::2], co_lnotab[1::2]):
+ addr += ord(addr_incr)
+ if addr > addrq:
+ return lineno
+ lineno += ord(line_incr)
+ return lineno
+
+
+class PyDictObjectPtr(PyObjectPtr):
+ """
+ Class wrapping a gdb.Value that's a PyDictObject* i.e. a dict instance
+ within the process being debugged.
+ """
+ _typename = 'PyDictObject'
+
+ def iteritems(self):
+ '''
+ Yields a sequence of (PyObjectPtr key, PyObjectPtr value) pairs,
analogous to dict.iteritems()
- '''
+ '''
keys = self.field('ma_keys')
values = self.field('ma_values')
entries, nentries = self._get_entries(keys)
@@ -682,41 +682,41 @@ class PyDictObjectPtr(PyObjectPtr):
pyop_value = PyObjectPtr.from_pyobject_ptr(values[i])
else:
pyop_value = PyObjectPtr.from_pyobject_ptr(ep['me_value'])
- if not pyop_value.is_null():
- pyop_key = PyObjectPtr.from_pyobject_ptr(ep['me_key'])
- yield (pyop_key, pyop_value)
-
- def proxyval(self, visited):
- # Guard against infinite loops:
- if self.as_address() in visited:
- return ProxyAlreadyVisited('{...}')
- visited.add(self.as_address())
-
- result = {}
+ if not pyop_value.is_null():
+ pyop_key = PyObjectPtr.from_pyobject_ptr(ep['me_key'])
+ yield (pyop_key, pyop_value)
+
+ def proxyval(self, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ return ProxyAlreadyVisited('{...}')
+ visited.add(self.as_address())
+
+ result = {}
for pyop_key, pyop_value in self.iteritems():
- proxy_key = pyop_key.proxyval(visited)
- proxy_value = pyop_value.proxyval(visited)
- result[proxy_key] = proxy_value
- return result
-
- def write_repr(self, out, visited):
- # Guard against infinite loops:
- if self.as_address() in visited:
- out.write('{...}')
- return
- visited.add(self.as_address())
-
- out.write('{')
- first = True
+ proxy_key = pyop_key.proxyval(visited)
+ proxy_value = pyop_value.proxyval(visited)
+ result[proxy_key] = proxy_value
+ return result
+
+ def write_repr(self, out, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ out.write('{...}')
+ return
+ visited.add(self.as_address())
+
+ out.write('{')
+ first = True
for pyop_key, pyop_value in self.iteritems():
- if not first:
- out.write(', ')
- first = False
- pyop_key.write_repr(out, visited)
- out.write(': ')
- pyop_value.write_repr(out, visited)
- out.write('}')
-
+ if not first:
+ out.write(', ')
+ first = False
+ pyop_key.write_repr(out, visited)
+ out.write(': ')
+ pyop_value.write_repr(out, visited)
+ out.write('}')
+
def _get_entries(self, keys):
dk_nentries = int(keys['dk_nentries'])
dk_size = int(keys['dk_size'])
@@ -726,7 +726,7 @@ class PyDictObjectPtr(PyObjectPtr):
except RuntimeError:
# >= Python 3.6
pass
-
+
if dk_size <= 0xFF:
offset = dk_size
elif dk_size <= 0xFFFF:
@@ -735,247 +735,247 @@ class PyDictObjectPtr(PyObjectPtr):
offset = 4 * dk_size
else:
offset = 8 * dk_size
-
+
ent_addr = keys['dk_indices']['as_1'].address
ent_addr = ent_addr.cast(_type_unsigned_char_ptr()) + offset
ent_ptr_t = gdb.lookup_type('PyDictKeyEntry').pointer()
ent_addr = ent_addr.cast(ent_ptr_t)
-
+
return ent_addr, dk_nentries
-
-
-class PyListObjectPtr(PyObjectPtr):
- _typename = 'PyListObject'
-
- def __getitem__(self, i):
- # Get the gdb.Value for the (PyObject*) with the given index:
- field_ob_item = self.field('ob_item')
- return field_ob_item[i]
-
- def proxyval(self, visited):
- # Guard against infinite loops:
- if self.as_address() in visited:
- return ProxyAlreadyVisited('[...]')
- visited.add(self.as_address())
-
- result = [PyObjectPtr.from_pyobject_ptr(self[i]).proxyval(visited)
- for i in safe_range(int_from_int(self.field('ob_size')))]
- return result
-
- def write_repr(self, out, visited):
- # Guard against infinite loops:
- if self.as_address() in visited:
- out.write('[...]')
- return
- visited.add(self.as_address())
-
- out.write('[')
- for i in safe_range(int_from_int(self.field('ob_size'))):
- if i > 0:
- out.write(', ')
- element = PyObjectPtr.from_pyobject_ptr(self[i])
- element.write_repr(out, visited)
- out.write(']')
-
-class PyLongObjectPtr(PyObjectPtr):
- _typename = 'PyLongObject'
-
- def proxyval(self, visited):
- '''
- Python's Include/longobjrep.h has this declaration:
- struct _longobject {
- PyObject_VAR_HEAD
- digit ob_digit[1];
- };
-
- with this description:
- The absolute value of a number is equal to
- SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i)
- Negative numbers are represented with ob_size < 0;
- zero is represented by ob_size == 0.
-
- where SHIFT can be either:
- #define PyLong_SHIFT 30
- #define PyLong_SHIFT 15
- '''
+
+
+class PyListObjectPtr(PyObjectPtr):
+ _typename = 'PyListObject'
+
+ def __getitem__(self, i):
+ # Get the gdb.Value for the (PyObject*) with the given index:
+ field_ob_item = self.field('ob_item')
+ return field_ob_item[i]
+
+ def proxyval(self, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ return ProxyAlreadyVisited('[...]')
+ visited.add(self.as_address())
+
+ result = [PyObjectPtr.from_pyobject_ptr(self[i]).proxyval(visited)
+ for i in safe_range(int_from_int(self.field('ob_size')))]
+ return result
+
+ def write_repr(self, out, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ out.write('[...]')
+ return
+ visited.add(self.as_address())
+
+ out.write('[')
+ for i in safe_range(int_from_int(self.field('ob_size'))):
+ if i > 0:
+ out.write(', ')
+ element = PyObjectPtr.from_pyobject_ptr(self[i])
+ element.write_repr(out, visited)
+ out.write(']')
+
+class PyLongObjectPtr(PyObjectPtr):
+ _typename = 'PyLongObject'
+
+ def proxyval(self, visited):
+ '''
+ Python's Include/longobjrep.h has this declaration:
+ struct _longobject {
+ PyObject_VAR_HEAD
+ digit ob_digit[1];
+ };
+
+ with this description:
+ The absolute value of a number is equal to
+ SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i)
+ Negative numbers are represented with ob_size < 0;
+ zero is represented by ob_size == 0.
+
+ where SHIFT can be either:
+ #define PyLong_SHIFT 30
+ #define PyLong_SHIFT 15
+ '''
ob_size = long(self.field('ob_size'))
- if ob_size == 0:
+ if ob_size == 0:
return 0
-
- ob_digit = self.field('ob_digit')
-
- if gdb.lookup_type('digit').sizeof == 2:
- SHIFT = 15
- else:
- SHIFT = 30
-
+
+ ob_digit = self.field('ob_digit')
+
+ if gdb.lookup_type('digit').sizeof == 2:
+ SHIFT = 15
+ else:
+ SHIFT = 30
+
digits = [long(ob_digit[i]) * 2**(SHIFT*i)
- for i in safe_range(abs(ob_size))]
- result = sum(digits)
- if ob_size < 0:
- result = -result
- return result
-
- def write_repr(self, out, visited):
- # Write this out as a Python 3 int literal, i.e. without the "L" suffix
- proxy = self.proxyval(visited)
- out.write("%s" % proxy)
-
-
-class PyBoolObjectPtr(PyLongObjectPtr):
- """
- Class wrapping a gdb.Value that's a PyBoolObject* i.e. one of the two
- <bool> instances (Py_True/Py_False) within the process being debugged.
- """
- def proxyval(self, visited):
+ for i in safe_range(abs(ob_size))]
+ result = sum(digits)
+ if ob_size < 0:
+ result = -result
+ return result
+
+ def write_repr(self, out, visited):
+ # Write this out as a Python 3 int literal, i.e. without the "L" suffix
+ proxy = self.proxyval(visited)
+ out.write("%s" % proxy)
+
+
+class PyBoolObjectPtr(PyLongObjectPtr):
+ """
+ Class wrapping a gdb.Value that's a PyBoolObject* i.e. one of the two
+ <bool> instances (Py_True/Py_False) within the process being debugged.
+ """
+ def proxyval(self, visited):
if PyLongObjectPtr.proxyval(self, visited):
return True
else:
return False
-
-class PyNoneStructPtr(PyObjectPtr):
- """
- Class wrapping a gdb.Value that's a PyObject* pointing to the
- singleton (we hope) _Py_NoneStruct with ob_type PyNone_Type
- """
- _typename = 'PyObject'
-
- def proxyval(self, visited):
- return None
-
-
-class PyFrameObjectPtr(PyObjectPtr):
- _typename = 'PyFrameObject'
-
- def __init__(self, gdbval, cast_to=None):
- PyObjectPtr.__init__(self, gdbval, cast_to)
-
- if not self.is_optimized_out():
- self.co = PyCodeObjectPtr.from_pyobject_ptr(self.field('f_code'))
- self.co_name = self.co.pyop_field('co_name')
- self.co_filename = self.co.pyop_field('co_filename')
-
- self.f_lineno = int_from_int(self.field('f_lineno'))
- self.f_lasti = int_from_int(self.field('f_lasti'))
- self.co_nlocals = int_from_int(self.co.field('co_nlocals'))
- self.co_varnames = PyTupleObjectPtr.from_pyobject_ptr(self.co.field('co_varnames'))
-
- def iter_locals(self):
- '''
- Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
- the local variables of this frame
- '''
- if self.is_optimized_out():
- return
-
- f_localsplus = self.field('f_localsplus')
- for i in safe_range(self.co_nlocals):
- pyop_value = PyObjectPtr.from_pyobject_ptr(f_localsplus[i])
- if not pyop_value.is_null():
- pyop_name = PyObjectPtr.from_pyobject_ptr(self.co_varnames[i])
- yield (pyop_name, pyop_value)
-
- def iter_globals(self):
- '''
- Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
- the global variables of this frame
- '''
- if self.is_optimized_out():
+
+class PyNoneStructPtr(PyObjectPtr):
+ """
+ Class wrapping a gdb.Value that's a PyObject* pointing to the
+ singleton (we hope) _Py_NoneStruct with ob_type PyNone_Type
+ """
+ _typename = 'PyObject'
+
+ def proxyval(self, visited):
+ return None
+
+
+class PyFrameObjectPtr(PyObjectPtr):
+ _typename = 'PyFrameObject'
+
+ def __init__(self, gdbval, cast_to=None):
+ PyObjectPtr.__init__(self, gdbval, cast_to)
+
+ if not self.is_optimized_out():
+ self.co = PyCodeObjectPtr.from_pyobject_ptr(self.field('f_code'))
+ self.co_name = self.co.pyop_field('co_name')
+ self.co_filename = self.co.pyop_field('co_filename')
+
+ self.f_lineno = int_from_int(self.field('f_lineno'))
+ self.f_lasti = int_from_int(self.field('f_lasti'))
+ self.co_nlocals = int_from_int(self.co.field('co_nlocals'))
+ self.co_varnames = PyTupleObjectPtr.from_pyobject_ptr(self.co.field('co_varnames'))
+
+ def iter_locals(self):
+ '''
+ Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
+ the local variables of this frame
+ '''
+ if self.is_optimized_out():
+ return
+
+ f_localsplus = self.field('f_localsplus')
+ for i in safe_range(self.co_nlocals):
+ pyop_value = PyObjectPtr.from_pyobject_ptr(f_localsplus[i])
+ if not pyop_value.is_null():
+ pyop_name = PyObjectPtr.from_pyobject_ptr(self.co_varnames[i])
+ yield (pyop_name, pyop_value)
+
+ def iter_globals(self):
+ '''
+ Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
+ the global variables of this frame
+ '''
+ if self.is_optimized_out():
return ()
-
- pyop_globals = self.pyop_field('f_globals')
+
+ pyop_globals = self.pyop_field('f_globals')
return pyop_globals.iteritems()
-
- def iter_builtins(self):
- '''
- Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
- the builtin variables
- '''
- if self.is_optimized_out():
+
+ def iter_builtins(self):
+ '''
+ Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
+ the builtin variables
+ '''
+ if self.is_optimized_out():
return ()
-
- pyop_builtins = self.pyop_field('f_builtins')
+
+ pyop_builtins = self.pyop_field('f_builtins')
return pyop_builtins.iteritems()
-
- def get_var_by_name(self, name):
- '''
- Look for the named local variable, returning a (PyObjectPtr, scope) pair
- where scope is a string 'local', 'global', 'builtin'
-
- If not found, return (None, None)
- '''
- for pyop_name, pyop_value in self.iter_locals():
- if name == pyop_name.proxyval(set()):
- return pyop_value, 'local'
- for pyop_name, pyop_value in self.iter_globals():
- if name == pyop_name.proxyval(set()):
- return pyop_value, 'global'
- for pyop_name, pyop_value in self.iter_builtins():
- if name == pyop_name.proxyval(set()):
- return pyop_value, 'builtin'
- return None, None
-
- def filename(self):
- '''Get the path of the current Python source file, as a string'''
- if self.is_optimized_out():
- return '(frame information optimized out)'
- return self.co_filename.proxyval(set())
-
- def current_line_num(self):
- '''Get current line number as an integer (1-based)
-
- Translated from PyFrame_GetLineNumber and PyCode_Addr2Line
-
- See Objects/lnotab_notes.txt
- '''
- if self.is_optimized_out():
- return None
- f_trace = self.field('f_trace')
+
+ def get_var_by_name(self, name):
+ '''
+ Look for the named local variable, returning a (PyObjectPtr, scope) pair
+ where scope is a string 'local', 'global', 'builtin'
+
+ If not found, return (None, None)
+ '''
+ for pyop_name, pyop_value in self.iter_locals():
+ if name == pyop_name.proxyval(set()):
+ return pyop_value, 'local'
+ for pyop_name, pyop_value in self.iter_globals():
+ if name == pyop_name.proxyval(set()):
+ return pyop_value, 'global'
+ for pyop_name, pyop_value in self.iter_builtins():
+ if name == pyop_name.proxyval(set()):
+ return pyop_value, 'builtin'
+ return None, None
+
+ def filename(self):
+ '''Get the path of the current Python source file, as a string'''
+ if self.is_optimized_out():
+ return '(frame information optimized out)'
+ return self.co_filename.proxyval(set())
+
+ def current_line_num(self):
+ '''Get current line number as an integer (1-based)
+
+ Translated from PyFrame_GetLineNumber and PyCode_Addr2Line
+
+ See Objects/lnotab_notes.txt
+ '''
+ if self.is_optimized_out():
+ return None
+ f_trace = self.field('f_trace')
if long(f_trace) != 0:
- # we have a non-NULL f_trace:
- return self.f_lineno
- else:
- #try:
- return self.co.addr2line(self.f_lasti)
- #except ValueError:
- # return self.f_lineno
-
- def current_line(self):
- '''Get the text of the current source line as a string, with a trailing
- newline character'''
- if self.is_optimized_out():
- return '(frame information optimized out)'
- filename = self.filename()
+ # we have a non-NULL f_trace:
+ return self.f_lineno
+ else:
+ #try:
+ return self.co.addr2line(self.f_lasti)
+ #except ValueError:
+ # return self.f_lineno
+
+ def current_line(self):
+ '''Get the text of the current source line as a string, with a trailing
+ newline character'''
+ if self.is_optimized_out():
+ return '(frame information optimized out)'
+ filename = self.filename()
try:
f = open(os_fsencode(filename), 'r')
except IOError:
return None
with f:
- all_lines = f.readlines()
- # Convert from 1-based current_line_num to 0-based list offset:
- return all_lines[self.current_line_num()-1]
-
- def write_repr(self, out, visited):
- if self.is_optimized_out():
- out.write('(frame information optimized out)')
- return
- out.write('Frame 0x%x, for file %s, line %i, in %s ('
- % (self.as_address(),
- self.co_filename.proxyval(visited),
- self.current_line_num(),
- self.co_name.proxyval(visited)))
- first = True
- for pyop_name, pyop_value in self.iter_locals():
- if not first:
- out.write(', ')
- first = False
-
- out.write(pyop_name.proxyval(visited))
- out.write('=')
- pyop_value.write_repr(out, visited)
-
- out.write(')')
-
+ all_lines = f.readlines()
+ # Convert from 1-based current_line_num to 0-based list offset:
+ return all_lines[self.current_line_num()-1]
+
+ def write_repr(self, out, visited):
+ if self.is_optimized_out():
+ out.write('(frame information optimized out)')
+ return
+ out.write('Frame 0x%x, for file %s, line %i, in %s ('
+ % (self.as_address(),
+ self.co_filename.proxyval(visited),
+ self.current_line_num(),
+ self.co_name.proxyval(visited)))
+ first = True
+ for pyop_name, pyop_value in self.iter_locals():
+ if not first:
+ out.write(', ')
+ first = False
+
+ out.write(pyop_name.proxyval(visited))
+ out.write('=')
+ pyop_value.write_repr(out, visited)
+
+ out.write(')')
+
def print_traceback(self):
if self.is_optimized_out():
sys.stdout.write(' (frame information optimized out)\n')
@@ -985,10 +985,10 @@ class PyFrameObjectPtr(PyObjectPtr):
% (self.co_filename.proxyval(visited),
self.current_line_num(),
self.co_name.proxyval(visited)))
-
-class PySetObjectPtr(PyObjectPtr):
- _typename = 'PySetObject'
-
+
+class PySetObjectPtr(PyObjectPtr):
+ _typename = 'PySetObject'
+
@classmethod
def _dummy_key(self):
return gdb.lookup_global_symbol('_PySet_Dummy').value()
@@ -1002,168 +1002,168 @@ class PySetObjectPtr(PyObjectPtr):
if key != 0 and key != dummy_ptr:
yield PyObjectPtr.from_pyobject_ptr(key)
- def proxyval(self, visited):
- # Guard against infinite loops:
- if self.as_address() in visited:
- return ProxyAlreadyVisited('%s(...)' % self.safe_tp_name())
- visited.add(self.as_address())
-
+ def proxyval(self, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ return ProxyAlreadyVisited('%s(...)' % self.safe_tp_name())
+ visited.add(self.as_address())
+
members = (key.proxyval(visited) for key in self)
- if self.safe_tp_name() == 'frozenset':
- return frozenset(members)
- else:
- return set(members)
-
- def write_repr(self, out, visited):
- # Emulate Python 3's set_repr
- tp_name = self.safe_tp_name()
-
- # Guard against infinite loops:
- if self.as_address() in visited:
- out.write('(...)')
- return
- visited.add(self.as_address())
-
- # Python 3's set_repr special-cases the empty set:
- if not self.field('used'):
- out.write(tp_name)
- out.write('()')
- return
-
- # Python 3 uses {} for set literals:
- if tp_name != 'set':
- out.write(tp_name)
- out.write('(')
-
- out.write('{')
- first = True
+ if self.safe_tp_name() == 'frozenset':
+ return frozenset(members)
+ else:
+ return set(members)
+
+ def write_repr(self, out, visited):
+ # Emulate Python 3's set_repr
+ tp_name = self.safe_tp_name()
+
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ out.write('(...)')
+ return
+ visited.add(self.as_address())
+
+ # Python 3's set_repr special-cases the empty set:
+ if not self.field('used'):
+ out.write(tp_name)
+ out.write('()')
+ return
+
+ # Python 3 uses {} for set literals:
+ if tp_name != 'set':
+ out.write(tp_name)
+ out.write('(')
+
+ out.write('{')
+ first = True
for key in self:
if not first:
out.write(', ')
first = False
key.write_repr(out, visited)
- out.write('}')
-
- if tp_name != 'set':
- out.write(')')
-
-
-class PyBytesObjectPtr(PyObjectPtr):
- _typename = 'PyBytesObject'
-
- def __str__(self):
- field_ob_size = self.field('ob_size')
- field_ob_sval = self.field('ob_sval')
+ out.write('}')
+
+ if tp_name != 'set':
+ out.write(')')
+
+
+class PyBytesObjectPtr(PyObjectPtr):
+ _typename = 'PyBytesObject'
+
+ def __str__(self):
+ field_ob_size = self.field('ob_size')
+ field_ob_sval = self.field('ob_sval')
char_ptr = field_ob_sval.address.cast(_type_unsigned_char_ptr())
return ''.join([chr(char_ptr[i]) for i in safe_range(field_ob_size)])
-
- def proxyval(self, visited):
- return str(self)
-
+
+ def proxyval(self, visited):
+ return str(self)
+
def write_repr(self, out, visited):
- # Write this out as a Python 3 bytes literal, i.e. with a "b" prefix
-
- # Get a PyStringObject* within the Python 2 gdb process:
- proxy = self.proxyval(visited)
-
- # Transliteration of Python 3's Objects/bytesobject.c:PyBytes_Repr
- # to Python 2 code:
- quote = "'"
- if "'" in proxy and not '"' in proxy:
- quote = '"'
+ # Write this out as a Python 3 bytes literal, i.e. with a "b" prefix
+
+ # Get a PyStringObject* within the Python 2 gdb process:
+ proxy = self.proxyval(visited)
+
+ # Transliteration of Python 3's Objects/bytesobject.c:PyBytes_Repr
+ # to Python 2 code:
+ quote = "'"
+ if "'" in proxy and not '"' in proxy:
+ quote = '"'
out.write('b')
- out.write(quote)
- for byte in proxy:
- if byte == quote or byte == '\\':
- out.write('\\')
- out.write(byte)
- elif byte == '\t':
- out.write('\\t')
- elif byte == '\n':
- out.write('\\n')
- elif byte == '\r':
- out.write('\\r')
- elif byte < ' ' or ord(byte) >= 0x7f:
- out.write('\\x')
- out.write(hexdigits[(ord(byte) & 0xf0) >> 4])
- out.write(hexdigits[ord(byte) & 0xf])
- else:
- out.write(byte)
- out.write(quote)
-
-
-class PyStringObjectPtr(PyBytesObjectPtr):
- _typename = 'PyStringObject'
-
-
-class PyTupleObjectPtr(PyObjectPtr):
- _typename = 'PyTupleObject'
-
- def __getitem__(self, i):
- # Get the gdb.Value for the (PyObject*) with the given index:
- field_ob_item = self.field('ob_item')
- return field_ob_item[i]
-
- def proxyval(self, visited):
- # Guard against infinite loops:
- if self.as_address() in visited:
- return ProxyAlreadyVisited('(...)')
- visited.add(self.as_address())
-
+ out.write(quote)
+ for byte in proxy:
+ if byte == quote or byte == '\\':
+ out.write('\\')
+ out.write(byte)
+ elif byte == '\t':
+ out.write('\\t')
+ elif byte == '\n':
+ out.write('\\n')
+ elif byte == '\r':
+ out.write('\\r')
+ elif byte < ' ' or ord(byte) >= 0x7f:
+ out.write('\\x')
+ out.write(hexdigits[(ord(byte) & 0xf0) >> 4])
+ out.write(hexdigits[ord(byte) & 0xf])
+ else:
+ out.write(byte)
+ out.write(quote)
+
+
+class PyStringObjectPtr(PyBytesObjectPtr):
+ _typename = 'PyStringObject'
+
+
+class PyTupleObjectPtr(PyObjectPtr):
+ _typename = 'PyTupleObject'
+
+ def __getitem__(self, i):
+ # Get the gdb.Value for the (PyObject*) with the given index:
+ field_ob_item = self.field('ob_item')
+ return field_ob_item[i]
+
+ def proxyval(self, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ return ProxyAlreadyVisited('(...)')
+ visited.add(self.as_address())
+
result = tuple(PyObjectPtr.from_pyobject_ptr(self[i]).proxyval(visited)
for i in safe_range(int_from_int(self.field('ob_size'))))
- return result
-
- def write_repr(self, out, visited):
- # Guard against infinite loops:
- if self.as_address() in visited:
- out.write('(...)')
- return
- visited.add(self.as_address())
-
- out.write('(')
- for i in safe_range(int_from_int(self.field('ob_size'))):
- if i > 0:
- out.write(', ')
- element = PyObjectPtr.from_pyobject_ptr(self[i])
- element.write_repr(out, visited)
- if self.field('ob_size') == 1:
- out.write(',)')
- else:
- out.write(')')
-
+ return result
+
+ def write_repr(self, out, visited):
+ # Guard against infinite loops:
+ if self.as_address() in visited:
+ out.write('(...)')
+ return
+ visited.add(self.as_address())
+
+ out.write('(')
+ for i in safe_range(int_from_int(self.field('ob_size'))):
+ if i > 0:
+ out.write(', ')
+ element = PyObjectPtr.from_pyobject_ptr(self[i])
+ element.write_repr(out, visited)
+ if self.field('ob_size') == 1:
+ out.write(',)')
+ else:
+ out.write(')')
+
class PyTypeObjectPtr(PyObjectPtr):
_typename = 'PyTypeObject'
-
-
-def _unichr_is_printable(char):
- # Logic adapted from Python 3's Tools/unicode/makeunicodedata.py
- if char == u" ":
- return True
- import unicodedata
- return unicodedata.category(char) not in ("C", "Z")
-
-if sys.maxunicode >= 0x10000:
+
+
+def _unichr_is_printable(char):
+ # Logic adapted from Python 3's Tools/unicode/makeunicodedata.py
+ if char == u" ":
+ return True
+ import unicodedata
+ return unicodedata.category(char) not in ("C", "Z")
+
+if sys.maxunicode >= 0x10000:
_unichr = unichr
-else:
- # Needed for proper surrogate support if sizeof(Py_UNICODE) is 2 in gdb
- def _unichr(x):
- if x < 0x10000:
- return unichr(x)
- x -= 0x10000
- ch1 = 0xD800 | (x >> 10)
- ch2 = 0xDC00 | (x & 0x3FF)
- return unichr(ch1) + unichr(ch2)
-
-
-class PyUnicodeObjectPtr(PyObjectPtr):
- _typename = 'PyUnicodeObject'
-
- def char_width(self):
- _type_Py_UNICODE = gdb.lookup_type('Py_UNICODE')
- return _type_Py_UNICODE.sizeof
-
- def proxyval(self, visited):
+else:
+ # Needed for proper surrogate support if sizeof(Py_UNICODE) is 2 in gdb
+ def _unichr(x):
+ if x < 0x10000:
+ return unichr(x)
+ x -= 0x10000
+ ch1 = 0xD800 | (x >> 10)
+ ch2 = 0xDC00 | (x & 0x3FF)
+ return unichr(ch1) + unichr(ch2)
+
+
+class PyUnicodeObjectPtr(PyObjectPtr):
+ _typename = 'PyUnicodeObject'
+
+ def char_width(self):
+ _type_Py_UNICODE = gdb.lookup_type('Py_UNICODE')
+ return _type_Py_UNICODE.sizeof
+
+ def proxyval(self, visited):
global _is_pep393
if _is_pep393 is None:
fields = gdb.lookup_type('PyUnicodeObject').target().fields()
@@ -1200,160 +1200,160 @@ class PyUnicodeObjectPtr(PyObjectPtr):
field_length = long(self.field('length'))
field_str = self.field('str')
may_have_surrogates = self.char_width() == 2
-
- # Gather a list of ints from the Py_UNICODE array; these are either
+
+ # Gather a list of ints from the Py_UNICODE array; these are either
# UCS-1, UCS-2 or UCS-4 code points:
if not may_have_surrogates:
- Py_UNICODEs = [int(field_str[i]) for i in safe_range(field_length)]
- else:
- # A more elaborate routine if sizeof(Py_UNICODE) is 2 in the
- # inferior process: we must join surrogate pairs.
- Py_UNICODEs = []
- i = 0
- limit = safety_limit(field_length)
- while i < limit:
- ucs = int(field_str[i])
- i += 1
- if ucs < 0xD800 or ucs >= 0xDC00 or i == field_length:
- Py_UNICODEs.append(ucs)
- continue
- # This could be a surrogate pair.
- ucs2 = int(field_str[i])
- if ucs2 < 0xDC00 or ucs2 > 0xDFFF:
- continue
- code = (ucs & 0x03FF) << 10
- code |= ucs2 & 0x03FF
- code += 0x00010000
- Py_UNICODEs.append(code)
- i += 1
-
- # Convert the int code points to unicode characters, and generate a
- # local unicode instance.
- # This splits surrogate pairs if sizeof(Py_UNICODE) is 2 here (in gdb).
+ Py_UNICODEs = [int(field_str[i]) for i in safe_range(field_length)]
+ else:
+ # A more elaborate routine if sizeof(Py_UNICODE) is 2 in the
+ # inferior process: we must join surrogate pairs.
+ Py_UNICODEs = []
+ i = 0
+ limit = safety_limit(field_length)
+ while i < limit:
+ ucs = int(field_str[i])
+ i += 1
+ if ucs < 0xD800 or ucs >= 0xDC00 or i == field_length:
+ Py_UNICODEs.append(ucs)
+ continue
+ # This could be a surrogate pair.
+ ucs2 = int(field_str[i])
+ if ucs2 < 0xDC00 or ucs2 > 0xDFFF:
+ continue
+ code = (ucs & 0x03FF) << 10
+ code |= ucs2 & 0x03FF
+ code += 0x00010000
+ Py_UNICODEs.append(code)
+ i += 1
+
+ # Convert the int code points to unicode characters, and generate a
+ # local unicode instance.
+ # This splits surrogate pairs if sizeof(Py_UNICODE) is 2 here (in gdb).
result = u''.join([
(_unichr(ucs) if ucs <= 0x10ffff else '\ufffd')
for ucs in Py_UNICODEs])
- return result
-
- def write_repr(self, out, visited):
+ return result
+
+ def write_repr(self, out, visited):
# Write this out as a Python 3 str literal, i.e. without a "u" prefix
- # Get a PyUnicodeObject* within the Python 2 gdb process:
- proxy = self.proxyval(visited)
-
- # Transliteration of Python 3's Object/unicodeobject.c:unicode_repr
- # to Python 2:
- if "'" in proxy and '"' not in proxy:
- quote = '"'
- else:
- quote = "'"
- out.write(quote)
-
- i = 0
- while i < len(proxy):
- ch = proxy[i]
- i += 1
-
- # Escape quotes and backslashes
- if ch == quote or ch == '\\':
- out.write('\\')
- out.write(ch)
-
- # Map special whitespace to '\t', \n', '\r'
- elif ch == '\t':
- out.write('\\t')
- elif ch == '\n':
- out.write('\\n')
- elif ch == '\r':
- out.write('\\r')
-
- # Map non-printable US ASCII to '\xhh' */
- elif ch < ' ' or ch == 0x7F:
- out.write('\\x')
- out.write(hexdigits[(ord(ch) >> 4) & 0x000F])
- out.write(hexdigits[ord(ch) & 0x000F])
-
- # Copy ASCII characters as-is
- elif ord(ch) < 0x7F:
- out.write(ch)
-
- # Non-ASCII characters
- else:
- ucs = ch
- ch2 = None
- if sys.maxunicode < 0x10000:
- # If sizeof(Py_UNICODE) is 2 here (in gdb), join
- # surrogate pairs before calling _unichr_is_printable.
- if (i < len(proxy)
- and 0xD800 <= ord(ch) < 0xDC00 \
- and 0xDC00 <= ord(proxy[i]) <= 0xDFFF):
- ch2 = proxy[i]
- ucs = ch + ch2
- i += 1
-
- # Unfortuately, Python 2's unicode type doesn't seem
- # to expose the "isprintable" method
- printable = _unichr_is_printable(ucs)
- if printable:
- try:
- ucs.encode(ENCODING)
- except UnicodeEncodeError:
- printable = False
-
- # Map Unicode whitespace and control characters
- # (categories Z* and C* except ASCII space)
- if not printable:
- if ch2 is not None:
- # Match Python 3's representation of non-printable
- # wide characters.
- code = (ord(ch) & 0x03FF) << 10
- code |= ord(ch2) & 0x03FF
- code += 0x00010000
- else:
- code = ord(ucs)
-
- # Map 8-bit characters to '\\xhh'
- if code <= 0xff:
- out.write('\\x')
- out.write(hexdigits[(code >> 4) & 0x000F])
- out.write(hexdigits[code & 0x000F])
- # Map 21-bit characters to '\U00xxxxxx'
- elif code >= 0x10000:
- out.write('\\U')
- out.write(hexdigits[(code >> 28) & 0x0000000F])
- out.write(hexdigits[(code >> 24) & 0x0000000F])
- out.write(hexdigits[(code >> 20) & 0x0000000F])
- out.write(hexdigits[(code >> 16) & 0x0000000F])
- out.write(hexdigits[(code >> 12) & 0x0000000F])
- out.write(hexdigits[(code >> 8) & 0x0000000F])
- out.write(hexdigits[(code >> 4) & 0x0000000F])
- out.write(hexdigits[code & 0x0000000F])
- # Map 16-bit characters to '\uxxxx'
- else:
- out.write('\\u')
- out.write(hexdigits[(code >> 12) & 0x000F])
- out.write(hexdigits[(code >> 8) & 0x000F])
- out.write(hexdigits[(code >> 4) & 0x000F])
- out.write(hexdigits[code & 0x000F])
- else:
- # Copy characters as-is
- out.write(ch)
- if ch2 is not None:
- out.write(ch2)
-
- out.write(quote)
-
-
+ # Get a PyUnicodeObject* within the Python 2 gdb process:
+ proxy = self.proxyval(visited)
+
+ # Transliteration of Python 3's Object/unicodeobject.c:unicode_repr
+ # to Python 2:
+ if "'" in proxy and '"' not in proxy:
+ quote = '"'
+ else:
+ quote = "'"
+ out.write(quote)
+
+ i = 0
+ while i < len(proxy):
+ ch = proxy[i]
+ i += 1
+
+ # Escape quotes and backslashes
+ if ch == quote or ch == '\\':
+ out.write('\\')
+ out.write(ch)
+
+ # Map special whitespace to '\t', \n', '\r'
+ elif ch == '\t':
+ out.write('\\t')
+ elif ch == '\n':
+ out.write('\\n')
+ elif ch == '\r':
+ out.write('\\r')
+
+ # Map non-printable US ASCII to '\xhh' */
+ elif ch < ' ' or ch == 0x7F:
+ out.write('\\x')
+ out.write(hexdigits[(ord(ch) >> 4) & 0x000F])
+ out.write(hexdigits[ord(ch) & 0x000F])
+
+ # Copy ASCII characters as-is
+ elif ord(ch) < 0x7F:
+ out.write(ch)
+
+ # Non-ASCII characters
+ else:
+ ucs = ch
+ ch2 = None
+ if sys.maxunicode < 0x10000:
+ # If sizeof(Py_UNICODE) is 2 here (in gdb), join
+ # surrogate pairs before calling _unichr_is_printable.
+ if (i < len(proxy)
+ and 0xD800 <= ord(ch) < 0xDC00 \
+ and 0xDC00 <= ord(proxy[i]) <= 0xDFFF):
+ ch2 = proxy[i]
+ ucs = ch + ch2
+ i += 1
+
+ # Unfortuately, Python 2's unicode type doesn't seem
+ # to expose the "isprintable" method
+ printable = _unichr_is_printable(ucs)
+ if printable:
+ try:
+ ucs.encode(ENCODING)
+ except UnicodeEncodeError:
+ printable = False
+
+ # Map Unicode whitespace and control characters
+ # (categories Z* and C* except ASCII space)
+ if not printable:
+ if ch2 is not None:
+ # Match Python 3's representation of non-printable
+ # wide characters.
+ code = (ord(ch) & 0x03FF) << 10
+ code |= ord(ch2) & 0x03FF
+ code += 0x00010000
+ else:
+ code = ord(ucs)
+
+ # Map 8-bit characters to '\\xhh'
+ if code <= 0xff:
+ out.write('\\x')
+ out.write(hexdigits[(code >> 4) & 0x000F])
+ out.write(hexdigits[code & 0x000F])
+ # Map 21-bit characters to '\U00xxxxxx'
+ elif code >= 0x10000:
+ out.write('\\U')
+ out.write(hexdigits[(code >> 28) & 0x0000000F])
+ out.write(hexdigits[(code >> 24) & 0x0000000F])
+ out.write(hexdigits[(code >> 20) & 0x0000000F])
+ out.write(hexdigits[(code >> 16) & 0x0000000F])
+ out.write(hexdigits[(code >> 12) & 0x0000000F])
+ out.write(hexdigits[(code >> 8) & 0x0000000F])
+ out.write(hexdigits[(code >> 4) & 0x0000000F])
+ out.write(hexdigits[code & 0x0000000F])
+ # Map 16-bit characters to '\uxxxx'
+ else:
+ out.write('\\u')
+ out.write(hexdigits[(code >> 12) & 0x000F])
+ out.write(hexdigits[(code >> 8) & 0x000F])
+ out.write(hexdigits[(code >> 4) & 0x000F])
+ out.write(hexdigits[code & 0x000F])
+ else:
+ # Copy characters as-is
+ out.write(ch)
+ if ch2 is not None:
+ out.write(ch2)
+
+ out.write(quote)
+
+
class wrapperobject(PyObjectPtr):
_typename = 'wrapperobject'
-
+
def safe_name(self):
try:
name = self.field('descr')['d_base']['name'].string()
return repr(name)
except (NullPyObjectPtr, RuntimeError):
return '<unknown name>'
-
+
def safe_tp_name(self):
try:
return self.field('self')['ob_type']['tp_name'].string()
@@ -1379,124 +1379,124 @@ class wrapperobject(PyObjectPtr):
out.write(proxy)
-def int_from_int(gdbval):
- return int(str(gdbval))
-
-
-def stringify(val):
- # TODO: repr() puts everything on one line; pformat can be nicer, but
- # can lead to v.long results; this function isolates the choice
- if True:
- return repr(val)
- else:
- from pprint import pformat
- return pformat(val)
-
-
-class PyObjectPtrPrinter:
- "Prints a (PyObject*)"
-
- def __init__ (self, gdbval):
- self.gdbval = gdbval
-
- def to_string (self):
- pyop = PyObjectPtr.from_pyobject_ptr(self.gdbval)
- if True:
- return pyop.get_truncated_repr(MAX_OUTPUT_LEN)
- else:
- # Generate full proxy value then stringify it.
- # Doing so could be expensive
- proxyval = pyop.proxyval(set())
- return stringify(proxyval)
-
-def pretty_printer_lookup(gdbval):
- type = gdbval.type.unqualified()
+def int_from_int(gdbval):
+ return int(str(gdbval))
+
+
+def stringify(val):
+ # TODO: repr() puts everything on one line; pformat can be nicer, but
+ # can lead to v.long results; this function isolates the choice
+ if True:
+ return repr(val)
+ else:
+ from pprint import pformat
+ return pformat(val)
+
+
+class PyObjectPtrPrinter:
+ "Prints a (PyObject*)"
+
+ def __init__ (self, gdbval):
+ self.gdbval = gdbval
+
+ def to_string (self):
+ pyop = PyObjectPtr.from_pyobject_ptr(self.gdbval)
+ if True:
+ return pyop.get_truncated_repr(MAX_OUTPUT_LEN)
+ else:
+ # Generate full proxy value then stringify it.
+ # Doing so could be expensive
+ proxyval = pyop.proxyval(set())
+ return stringify(proxyval)
+
+def pretty_printer_lookup(gdbval):
+ type = gdbval.type.unqualified()
if type.code != gdb.TYPE_CODE_PTR:
return None
-
+
type = type.target().unqualified()
t = str(type)
if t in ("PyObject", "PyFrameObject", "PyUnicodeObject", "wrapperobject"):
return PyObjectPtrPrinter(gdbval)
-"""
-During development, I've been manually invoking the code in this way:
-(gdb) python
-
-import sys
-sys.path.append('/home/david/coding/python-gdb')
-import libpython
-end
-
-then reloading it after each edit like this:
-(gdb) python reload(libpython)
-
-The following code should ensure that the prettyprinter is registered
-if the code is autoloaded by gdb when visiting libpython.so, provided
-that this python file is installed to the same path as the library (or its
-.debug file) plus a "-gdb.py" suffix, e.g:
- /usr/lib/libpython2.6.so.1.0-gdb.py
- /usr/lib/debug/usr/lib/libpython2.6.so.1.0.debug-gdb.py
-"""
+"""
+During development, I've been manually invoking the code in this way:
+(gdb) python
+
+import sys
+sys.path.append('/home/david/coding/python-gdb')
+import libpython
+end
+
+then reloading it after each edit like this:
+(gdb) python reload(libpython)
+
+The following code should ensure that the prettyprinter is registered
+if the code is autoloaded by gdb when visiting libpython.so, provided
+that this python file is installed to the same path as the library (or its
+.debug file) plus a "-gdb.py" suffix, e.g:
+ /usr/lib/libpython2.6.so.1.0-gdb.py
+ /usr/lib/debug/usr/lib/libpython2.6.so.1.0.debug-gdb.py
+"""
def register (obj):
- if obj is None:
- obj = gdb
-
- # Wire up the pretty-printer
- obj.pretty_printers.append(pretty_printer_lookup)
-
+ if obj is None:
+ obj = gdb
+
+ # Wire up the pretty-printer
+ obj.pretty_printers.append(pretty_printer_lookup)
+
register (gdb.current_objfile ())
-
-
-
-# Unfortunately, the exact API exposed by the gdb module varies somewhat
-# from build to build
-# See http://bugs.python.org/issue8279?#msg102276
-
-class Frame(object):
- '''
- Wrapper for gdb.Frame, adding various methods
- '''
- def __init__(self, gdbframe):
- self._gdbframe = gdbframe
-
- def older(self):
- older = self._gdbframe.older()
- if older:
- return Frame(older)
- else:
- return None
-
- def newer(self):
- newer = self._gdbframe.newer()
- if newer:
- return Frame(newer)
- else:
- return None
-
- def select(self):
- '''If supported, select this frame and return True; return False if unsupported
-
- Not all builds have a gdb.Frame.select method; seems to be present on Fedora 12
- onwards, but absent on Ubuntu buildbot'''
- if not hasattr(self._gdbframe, 'select'):
- print ('Unable to select frame: '
- 'this build of gdb does not expose a gdb.Frame.select method')
- return False
- self._gdbframe.select()
- return True
-
- def get_index(self):
- '''Calculate index of frame, starting at 0 for the newest frame within
- this thread'''
- index = 0
- # Go down until you reach the newest frame:
- iter_frame = self
- while iter_frame.newer():
- index += 1
- iter_frame = iter_frame.newer()
- return index
-
+
+
+
+# Unfortunately, the exact API exposed by the gdb module varies somewhat
+# from build to build
+# See http://bugs.python.org/issue8279?#msg102276
+
+class Frame(object):
+ '''
+ Wrapper for gdb.Frame, adding various methods
+ '''
+ def __init__(self, gdbframe):
+ self._gdbframe = gdbframe
+
+ def older(self):
+ older = self._gdbframe.older()
+ if older:
+ return Frame(older)
+ else:
+ return None
+
+ def newer(self):
+ newer = self._gdbframe.newer()
+ if newer:
+ return Frame(newer)
+ else:
+ return None
+
+ def select(self):
+ '''If supported, select this frame and return True; return False if unsupported
+
+ Not all builds have a gdb.Frame.select method; seems to be present on Fedora 12
+ onwards, but absent on Ubuntu buildbot'''
+ if not hasattr(self._gdbframe, 'select'):
+ print ('Unable to select frame: '
+ 'this build of gdb does not expose a gdb.Frame.select method')
+ return False
+ self._gdbframe.select()
+ return True
+
+ def get_index(self):
+ '''Calculate index of frame, starting at 0 for the newest frame within
+ this thread'''
+ index = 0
+ # Go down until you reach the newest frame:
+ iter_frame = self
+ while iter_frame.newer():
+ index += 1
+ iter_frame = iter_frame.newer()
+ return index
+
# We divide frames into:
# - "python frames":
# - "bytecode frames" i.e. PyEval_EvalFrameEx
@@ -1517,19 +1517,19 @@ class Frame(object):
def is_evalframe(self):
'''Is this a _PyEval_EvalFrameDefault frame?'''
if self._gdbframe.name() == EVALFRAME:
- '''
- I believe we also need to filter on the inline
- struct frame_id.inline_depth, only regarding frames with
- an inline depth of 0 as actually being this function
-
- So we reject those with type gdb.INLINE_FRAME
- '''
- if self._gdbframe.type() == gdb.NORMAL_FRAME:
+ '''
+ I believe we also need to filter on the inline
+ struct frame_id.inline_depth, only regarding frames with
+ an inline depth of 0 as actually being this function
+
+ So we reject those with type gdb.INLINE_FRAME
+ '''
+ if self._gdbframe.type() == gdb.NORMAL_FRAME:
# We have a _PyEval_EvalFrameDefault frame:
- return True
-
- return False
-
+ return True
+
+ return False
+
def is_other_python_frame(self):
'''Is this frame worth displaying in python backtraces?
Examples:
@@ -1541,10 +1541,10 @@ class Frame(object):
'''
if self.is_waiting_for_gil():
return 'Waiting for the GIL'
-
+
if self.is_gc_collect():
return 'Garbage-collecting'
-
+
# Detect invocations of PyCFunction instances:
frame = self._gdbframe
caller = frame.name()
@@ -1559,23 +1559,23 @@ class Frame(object):
# PyCFunctionObject instance
# "f" is the same value, but cast to (PyCFunctionObject*)
# "self" is the (PyObject*) of the 'self'
- try:
+ try:
# Use the prettyprinter for the func:
func = frame.read_var(arg_name)
return str(func)
except RuntimeError:
return 'PyCFunction invocation (unable to read %s)' % arg_name
-
+
if caller == 'wrapper_call':
try:
func = frame.read_var('wp')
return str(func)
except RuntimeError:
return '<wrapper_call invocation>'
-
+
# This frame isn't worth reporting:
return False
-
+
def is_waiting_for_gil(self):
'''Is this frame waiting on the GIL?'''
# This assumes the _POSIX_THREADS version of Python/ceval_gil.h:
@@ -1587,8 +1587,8 @@ class Frame(object):
'''Is this frame "collect" within the garbage-collector?'''
return self._gdbframe.name() == 'collect'
- def get_pyop(self):
- try:
+ def get_pyop(self):
+ try:
f = self._gdbframe.read_var('f')
frame = PyFrameObjectPtr.from_pyobject_ptr(f)
if not frame.is_optimized_out():
@@ -1605,17 +1605,17 @@ class Frame(object):
return frame
return orig_frame
except ValueError:
- return None
-
- @classmethod
- def get_selected_frame(cls):
- _gdbframe = gdb.selected_frame()
- if _gdbframe:
- return Frame(_gdbframe)
- return None
-
- @classmethod
- def get_selected_python_frame(cls):
+ return None
+
+ @classmethod
+ def get_selected_frame(cls):
+ _gdbframe = gdb.selected_frame()
+ if _gdbframe:
+ return Frame(_gdbframe)
+ return None
+
+ @classmethod
+ def get_selected_python_frame(cls):
'''Try to obtain the Frame for the python-related code in the selected
frame, or None'''
try:
@@ -1636,35 +1636,35 @@ class Frame(object):
def get_selected_bytecode_frame(cls):
'''Try to obtain the Frame for the python bytecode interpreter in the
selected GDB frame, or None'''
- frame = cls.get_selected_frame()
-
- while frame:
+ frame = cls.get_selected_frame()
+
+ while frame:
if frame.is_evalframe():
- return frame
- frame = frame.older()
-
- # Not found:
- return None
-
- def print_summary(self):
+ return frame
+ frame = frame.older()
+
+ # Not found:
+ return None
+
+ def print_summary(self):
if self.is_evalframe():
- pyop = self.get_pyop()
- if pyop:
- line = pyop.get_truncated_repr(MAX_OUTPUT_LEN)
- write_unicode(sys.stdout, '#%i %s\n' % (self.get_index(), line))
+ pyop = self.get_pyop()
+ if pyop:
+ line = pyop.get_truncated_repr(MAX_OUTPUT_LEN)
+ write_unicode(sys.stdout, '#%i %s\n' % (self.get_index(), line))
if not pyop.is_optimized_out():
line = pyop.current_line()
if line is not None:
sys.stdout.write(' %s\n' % line.strip())
- else:
- sys.stdout.write('#%i (unable to read python frame information)\n' % self.get_index())
- else:
+ else:
+ sys.stdout.write('#%i (unable to read python frame information)\n' % self.get_index())
+ else:
info = self.is_other_python_frame()
if info:
sys.stdout.write('#%i %s\n' % (self.get_index(), info))
else:
sys.stdout.write('#%i\n' % self.get_index())
-
+
def print_traceback(self):
if self.is_evalframe():
pyop = self.get_pyop()
@@ -1682,62 +1682,62 @@ class Frame(object):
sys.stdout.write(' %s\n' % info)
else:
sys.stdout.write(' (not a python frame)\n')
-
-class PyList(gdb.Command):
- '''List the current Python source code, if any
-
- Use
- py-list START
- to list at a different line number within the python source.
-
- Use
- py-list START, END
- to list a specific range of lines within the python source.
- '''
-
- def __init__(self):
- gdb.Command.__init__ (self,
- "py-list",
- gdb.COMMAND_FILES,
- gdb.COMPLETE_NONE)
-
-
- def invoke(self, args, from_tty):
- import re
-
- start = None
- end = None
-
- m = re.match(r'\s*(\d+)\s*', args)
- if m:
- start = int(m.group(0))
- end = start + 10
-
- m = re.match(r'\s*(\d+)\s*,\s*(\d+)\s*', args)
- if m:
- start, end = map(int, m.groups())
-
+
+class PyList(gdb.Command):
+ '''List the current Python source code, if any
+
+ Use
+ py-list START
+ to list at a different line number within the python source.
+
+ Use
+ py-list START, END
+ to list a specific range of lines within the python source.
+ '''
+
+ def __init__(self):
+ gdb.Command.__init__ (self,
+ "py-list",
+ gdb.COMMAND_FILES,
+ gdb.COMPLETE_NONE)
+
+
+ def invoke(self, args, from_tty):
+ import re
+
+ start = None
+ end = None
+
+ m = re.match(r'\s*(\d+)\s*', args)
+ if m:
+ start = int(m.group(0))
+ end = start + 10
+
+ m = re.match(r'\s*(\d+)\s*,\s*(\d+)\s*', args)
+ if m:
+ start, end = map(int, m.groups())
+
# py-list requires an actual PyEval_EvalFrameEx frame:
frame = Frame.get_selected_bytecode_frame()
- if not frame:
+ if not frame:
print('Unable to locate gdb frame for python bytecode interpreter')
- return
-
- pyop = frame.get_pyop()
+ return
+
+ pyop = frame.get_pyop()
if not pyop or pyop.is_optimized_out():
- print('Unable to read information on python frame')
- return
-
- filename = pyop.filename()
- lineno = pyop.current_line_num()
-
- if start is None:
- start = lineno - 5
- end = lineno + 5
-
- if start<1:
- start = 1
-
+ print('Unable to read information on python frame')
+ return
+
+ filename = pyop.filename()
+ lineno = pyop.current_line_num()
+
+ if start is None:
+ start = lineno - 5
+ end = lineno + 5
+
+ if start<1:
+ start = 1
+
try:
f = open(os_fsencode(filename), 'r')
except IOError as err:
@@ -1745,79 +1745,79 @@ class PyList(gdb.Command):
% (filename, err))
return
with f:
- all_lines = f.readlines()
- # start and end are 1-based, all_lines is 0-based;
- # so [start-1:end] as a python slice gives us [start, end] as a
- # closed interval
- for i, line in enumerate(all_lines[start-1:end]):
- linestr = str(i+start)
- # Highlight current line:
- if i + start == lineno:
- linestr = '>' + linestr
- sys.stdout.write('%4s %s' % (linestr, line))
-
-
-# ...and register the command:
-PyList()
-
-def move_in_stack(move_up):
- '''Move up or down the stack (for the py-up/py-down command)'''
- frame = Frame.get_selected_python_frame()
+ all_lines = f.readlines()
+ # start and end are 1-based, all_lines is 0-based;
+ # so [start-1:end] as a python slice gives us [start, end] as a
+ # closed interval
+ for i, line in enumerate(all_lines[start-1:end]):
+ linestr = str(i+start)
+ # Highlight current line:
+ if i + start == lineno:
+ linestr = '>' + linestr
+ sys.stdout.write('%4s %s' % (linestr, line))
+
+
+# ...and register the command:
+PyList()
+
+def move_in_stack(move_up):
+ '''Move up or down the stack (for the py-up/py-down command)'''
+ frame = Frame.get_selected_python_frame()
if not frame:
print('Unable to locate python frame')
return
- while frame:
- if move_up:
- iter_frame = frame.older()
- else:
- iter_frame = frame.newer()
-
- if not iter_frame:
- break
-
+ while frame:
+ if move_up:
+ iter_frame = frame.older()
+ else:
+ iter_frame = frame.newer()
+
+ if not iter_frame:
+ break
+
if iter_frame.is_python_frame():
- # Result:
- if iter_frame.select():
- iter_frame.print_summary()
- return
-
- frame = iter_frame
-
- if move_up:
- print('Unable to find an older python frame')
- else:
- print('Unable to find a newer python frame')
-
-class PyUp(gdb.Command):
- 'Select and print the python stack frame that called this one (if any)'
- def __init__(self):
- gdb.Command.__init__ (self,
- "py-up",
- gdb.COMMAND_STACK,
- gdb.COMPLETE_NONE)
-
-
- def invoke(self, args, from_tty):
- move_in_stack(move_up=True)
-
-class PyDown(gdb.Command):
- 'Select and print the python stack frame called by this one (if any)'
- def __init__(self):
- gdb.Command.__init__ (self,
- "py-down",
- gdb.COMMAND_STACK,
- gdb.COMPLETE_NONE)
-
-
- def invoke(self, args, from_tty):
- move_in_stack(move_up=False)
-
-# Not all builds of gdb have gdb.Frame.select
-if hasattr(gdb.Frame, 'select'):
- PyUp()
- PyDown()
-
+ # Result:
+ if iter_frame.select():
+ iter_frame.print_summary()
+ return
+
+ frame = iter_frame
+
+ if move_up:
+ print('Unable to find an older python frame')
+ else:
+ print('Unable to find a newer python frame')
+
+class PyUp(gdb.Command):
+ 'Select and print the python stack frame that called this one (if any)'
+ def __init__(self):
+ gdb.Command.__init__ (self,
+ "py-up",
+ gdb.COMMAND_STACK,
+ gdb.COMPLETE_NONE)
+
+
+ def invoke(self, args, from_tty):
+ move_in_stack(move_up=True)
+
+class PyDown(gdb.Command):
+ 'Select and print the python stack frame called by this one (if any)'
+ def __init__(self):
+ gdb.Command.__init__ (self,
+ "py-down",
+ gdb.COMMAND_STACK,
+ gdb.COMPLETE_NONE)
+
+
+ def invoke(self, args, from_tty):
+ move_in_stack(move_up=False)
+
+# Not all builds of gdb have gdb.Frame.select
+if hasattr(gdb.Frame, 'select'):
+ PyUp()
+ PyDown()
+
class PyBacktraceFull(gdb.Command):
'Display the current python frame and all the frames within its call stack (if any)'
def __init__(self):
@@ -1825,7 +1825,7 @@ class PyBacktraceFull(gdb.Command):
"py-bt-full",
gdb.COMMAND_STACK,
gdb.COMPLETE_NONE)
-
+
def invoke(self, args, from_tty):
frame = Frame.get_selected_python_frame()
@@ -1840,101 +1840,101 @@ class PyBacktraceFull(gdb.Command):
PyBacktraceFull()
-class PyBacktrace(gdb.Command):
- 'Display the current python frame and all the frames within its call stack (if any)'
- def __init__(self):
- gdb.Command.__init__ (self,
- "py-bt",
- gdb.COMMAND_STACK,
- gdb.COMPLETE_NONE)
-
-
- def invoke(self, args, from_tty):
- frame = Frame.get_selected_python_frame()
+class PyBacktrace(gdb.Command):
+ 'Display the current python frame and all the frames within its call stack (if any)'
+ def __init__(self):
+ gdb.Command.__init__ (self,
+ "py-bt",
+ gdb.COMMAND_STACK,
+ gdb.COMPLETE_NONE)
+
+
+ def invoke(self, args, from_tty):
+ frame = Frame.get_selected_python_frame()
if not frame:
print('Unable to locate python frame')
return
sys.stdout.write('Traceback (most recent call first):\n')
- while frame:
+ while frame:
if frame.is_python_frame():
frame.print_traceback()
- frame = frame.older()
-
-PyBacktrace()
-
-class PyPrint(gdb.Command):
- 'Look up the given python variable name, and print it'
- def __init__(self):
- gdb.Command.__init__ (self,
- "py-print",
- gdb.COMMAND_DATA,
- gdb.COMPLETE_NONE)
-
-
- def invoke(self, args, from_tty):
- name = str(args)
-
- frame = Frame.get_selected_python_frame()
- if not frame:
- print('Unable to locate python frame')
- return
-
- pyop_frame = frame.get_pyop()
- if not pyop_frame:
- print('Unable to read information on python frame')
- return
-
- pyop_var, scope = pyop_frame.get_var_by_name(name)
-
- if pyop_var:
+ frame = frame.older()
+
+PyBacktrace()
+
+class PyPrint(gdb.Command):
+ 'Look up the given python variable name, and print it'
+ def __init__(self):
+ gdb.Command.__init__ (self,
+ "py-print",
+ gdb.COMMAND_DATA,
+ gdb.COMPLETE_NONE)
+
+
+ def invoke(self, args, from_tty):
+ name = str(args)
+
+ frame = Frame.get_selected_python_frame()
+ if not frame:
+ print('Unable to locate python frame')
+ return
+
+ pyop_frame = frame.get_pyop()
+ if not pyop_frame:
+ print('Unable to read information on python frame')
+ return
+
+ pyop_var, scope = pyop_frame.get_var_by_name(name)
+
+ if pyop_var:
print('%s %r = %s'
% (scope,
name,
pyop_var.get_truncated_repr(MAX_OUTPUT_LEN)))
- else:
- print('%r not found' % name)
-
-PyPrint()
-
-class PyLocals(gdb.Command):
- 'Look up the given python variable name, and print it'
+ else:
+ print('%r not found' % name)
+
+PyPrint()
+
+class PyLocals(gdb.Command):
+ 'Look up the given python variable name, and print it'
def __init__(self, command="py-locals"):
gdb.Command.__init__ (self,
command,
gdb.COMMAND_DATA,
gdb.COMPLETE_NONE)
-
-
- def invoke(self, args, from_tty):
- name = str(args)
-
- frame = Frame.get_selected_python_frame()
- if not frame:
- print('Unable to locate python frame')
- return
-
- pyop_frame = frame.get_pyop()
- if not pyop_frame:
- print('Unable to read information on python frame')
- return
-
- namespace = self.get_namespace(pyop_frame)
- namespace = [(name.proxyval(set()), val) for name, val in namespace]
-
- if namespace:
- name, val = max(namespace, key=lambda item: len(item[0]))
- max_name_length = len(name)
-
- for name, pyop_value in namespace:
- value = pyop_value.get_truncated_repr(MAX_OUTPUT_LEN)
- print('%-*s = %s' % (max_name_length, name, value))
-
- def get_namespace(self, pyop_frame):
- return pyop_frame.iter_locals()
-
+
+
+ def invoke(self, args, from_tty):
+ name = str(args)
+
+ frame = Frame.get_selected_python_frame()
+ if not frame:
+ print('Unable to locate python frame')
+ return
+
+ pyop_frame = frame.get_pyop()
+ if not pyop_frame:
+ print('Unable to read information on python frame')
+ return
+
+ namespace = self.get_namespace(pyop_frame)
+ namespace = [(name.proxyval(set()), val) for name, val in namespace]
+
+ if namespace:
+ name, val = max(namespace, key=lambda item: len(item[0]))
+ max_name_length = len(name)
+
+ for name, pyop_value in namespace:
+ value = pyop_value.get_truncated_repr(MAX_OUTPUT_LEN)
+ print('%-*s = %s' % (max_name_length, name, value))
+
+ def get_namespace(self, pyop_frame):
+ return pyop_frame.iter_locals()
+
PyLocals()
-
+
##################################################################
## added, not in CPython
@@ -1946,815 +1946,815 @@ import tempfile
import textwrap
import itertools
-class PyGlobals(PyLocals):
- 'List all the globals in the currently select Python frame'
-
- def get_namespace(self, pyop_frame):
- return pyop_frame.iter_globals()
-
-
+class PyGlobals(PyLocals):
+ 'List all the globals in the currently select Python frame'
+
+ def get_namespace(self, pyop_frame):
+ return pyop_frame.iter_globals()
+
+
PyGlobals("py-globals")
-
-
-class PyNameEquals(gdb.Function):
-
- def _get_pycurframe_attr(self, attr):
- frame = Frame(gdb.selected_frame())
- if frame.is_evalframeex():
- pyframe = frame.get_pyop()
- if pyframe is None:
- warnings.warn("Use a Python debug build, Python breakpoints "
- "won't work otherwise.")
- return None
-
- return getattr(pyframe, attr).proxyval(set())
-
- return None
-
- def invoke(self, funcname):
- attr = self._get_pycurframe_attr('co_name')
- return attr is not None and attr == funcname.string()
-
-PyNameEquals("pyname_equals")
-
-
-class PyModEquals(PyNameEquals):
-
- def invoke(self, modname):
- attr = self._get_pycurframe_attr('co_filename')
- if attr is not None:
- filename, ext = os.path.splitext(os.path.basename(attr))
- return filename == modname.string()
- return False
-
-PyModEquals("pymod_equals")
-
-
-class PyBreak(gdb.Command):
- """
- Set a Python breakpoint. Examples:
-
- Break on any function or method named 'func' in module 'modname'
-
- py-break modname.func
-
- Break on any function or method named 'func'
-
- py-break func
- """
-
- def invoke(self, funcname, from_tty):
- if '.' in funcname:
- modname, dot, funcname = funcname.rpartition('.')
- cond = '$pyname_equals("%s") && $pymod_equals("%s")' % (funcname,
- modname)
- else:
- cond = '$pyname_equals("%s")' % funcname
-
- gdb.execute('break PyEval_EvalFrameEx if ' + cond)
-
-PyBreak("py-break", gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE)
-
-
-class _LoggingState(object):
- """
- State that helps to provide a reentrant gdb.execute() function.
- """
-
- def __init__(self):
+
+
+class PyNameEquals(gdb.Function):
+
+ def _get_pycurframe_attr(self, attr):
+ frame = Frame(gdb.selected_frame())
+ if frame.is_evalframeex():
+ pyframe = frame.get_pyop()
+ if pyframe is None:
+ warnings.warn("Use a Python debug build, Python breakpoints "
+ "won't work otherwise.")
+ return None
+
+ return getattr(pyframe, attr).proxyval(set())
+
+ return None
+
+ def invoke(self, funcname):
+ attr = self._get_pycurframe_attr('co_name')
+ return attr is not None and attr == funcname.string()
+
+PyNameEquals("pyname_equals")
+
+
+class PyModEquals(PyNameEquals):
+
+ def invoke(self, modname):
+ attr = self._get_pycurframe_attr('co_filename')
+ if attr is not None:
+ filename, ext = os.path.splitext(os.path.basename(attr))
+ return filename == modname.string()
+ return False
+
+PyModEquals("pymod_equals")
+
+
+class PyBreak(gdb.Command):
+ """
+ Set a Python breakpoint. Examples:
+
+ Break on any function or method named 'func' in module 'modname'
+
+ py-break modname.func
+
+ Break on any function or method named 'func'
+
+ py-break func
+ """
+
+ def invoke(self, funcname, from_tty):
+ if '.' in funcname:
+ modname, dot, funcname = funcname.rpartition('.')
+ cond = '$pyname_equals("%s") && $pymod_equals("%s")' % (funcname,
+ modname)
+ else:
+ cond = '$pyname_equals("%s")' % funcname
+
+ gdb.execute('break PyEval_EvalFrameEx if ' + cond)
+
+PyBreak("py-break", gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE)
+
+
+class _LoggingState(object):
+ """
+ State that helps to provide a reentrant gdb.execute() function.
+ """
+
+ def __init__(self):
f = tempfile.NamedTemporaryFile('r+')
self.file = f
self.filename = f.name
self.fd = f.fileno()
- _execute("set logging file %s" % self.filename)
- self.file_position_stack = []
-
- def __enter__(self):
- if not self.file_position_stack:
- _execute("set logging redirect on")
- _execute("set logging on")
- _execute("set pagination off")
-
- self.file_position_stack.append(os.fstat(self.fd).st_size)
- return self
-
- def getoutput(self):
- gdb.flush()
- self.file.seek(self.file_position_stack[-1])
- result = self.file.read()
- return result
-
- def __exit__(self, exc_type, exc_val, tb):
- startpos = self.file_position_stack.pop()
- self.file.seek(startpos)
- self.file.truncate()
- if not self.file_position_stack:
- _execute("set logging off")
- _execute("set logging redirect off")
- _execute("set pagination on")
-
-
-def execute(command, from_tty=False, to_string=False):
- """
- Replace gdb.execute() with this function and have it accept a 'to_string'
- argument (new in 7.2). Have it properly capture stderr also. Ensure
- reentrancy.
- """
- if to_string:
- with _logging_state as state:
- _execute(command, from_tty)
- return state.getoutput()
- else:
- _execute(command, from_tty)
-
-
-_execute = gdb.execute
-gdb.execute = execute
-_logging_state = _LoggingState()
-
-
-def get_selected_inferior():
- """
- Return the selected inferior in gdb.
- """
- # Woooh, another bug in gdb! Is there an end in sight?
- # http://sourceware.org/bugzilla/show_bug.cgi?id=12212
- return gdb.inferiors()[0]
-
- selected_thread = gdb.selected_thread()
-
- for inferior in gdb.inferiors():
- for thread in inferior.threads():
- if thread == selected_thread:
- return inferior
-
-
-def source_gdb_script(script_contents, to_string=False):
- """
- Source a gdb script with script_contents passed as a string. This is useful
- to provide defines for py-step and py-next to make them repeatable (this is
- not possible with gdb.execute()). See
- http://sourceware.org/bugzilla/show_bug.cgi?id=12216
- """
- fd, filename = tempfile.mkstemp()
- f = os.fdopen(fd, 'w')
- f.write(script_contents)
- f.close()
- gdb.execute("source %s" % filename, to_string=to_string)
- os.remove(filename)
-
-
-def register_defines():
- source_gdb_script(textwrap.dedent("""\
- define py-step
- -py-step
- end
-
- define py-next
- -py-next
- end
-
- document py-step
- %s
- end
-
- document py-next
- %s
- end
- """) % (PyStep.__doc__, PyNext.__doc__))
-
-
-def stackdepth(frame):
- "Tells the stackdepth of a gdb frame."
- depth = 0
- while frame:
- frame = frame.older()
- depth += 1
-
- return depth
-
-
-class ExecutionControlCommandBase(gdb.Command):
- """
- Superclass for language specific execution control. Language specific
- features should be implemented by lang_info using the LanguageInfo
- interface. 'name' is the name of the command.
- """
-
- def __init__(self, name, lang_info):
- super(ExecutionControlCommandBase, self).__init__(
- name, gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE)
- self.lang_info = lang_info
-
- def install_breakpoints(self):
- all_locations = itertools.chain(
- self.lang_info.static_break_functions(),
- self.lang_info.runtime_break_functions())
-
- for location in all_locations:
- result = gdb.execute('break %s' % location, to_string=True)
- yield re.search(r'Breakpoint (\d+)', result).group(1)
-
- def delete_breakpoints(self, breakpoint_list):
- for bp in breakpoint_list:
- gdb.execute("delete %s" % bp)
-
- def filter_output(self, result):
- reflags = re.MULTILINE
-
- output_on_halt = [
- (r'^Program received signal .*', reflags|re.DOTALL),
- (r'.*[Ww]arning.*', 0),
- (r'^Program exited .*', reflags),
- ]
-
- output_always = [
- # output when halting on a watchpoint
- (r'^(Old|New) value = .*', reflags),
- # output from the 'display' command
- (r'^\d+: \w+ = .*', reflags),
- ]
-
- def filter_output(regexes):
- output = []
- for regex, flags in regexes:
- for match in re.finditer(regex, result, flags):
- output.append(match.group(0))
-
- return '\n'.join(output)
-
- # Filter the return value output of the 'finish' command
- match_finish = re.search(r'^Value returned is \$\d+ = (.*)', result,
- re.MULTILINE)
- if match_finish:
- finish_output = 'Value returned: %s\n' % match_finish.group(1)
- else:
- finish_output = ''
-
- return (filter_output(output_on_halt),
- finish_output + filter_output(output_always))
-
- def stopped(self):
- return get_selected_inferior().pid == 0
-
- def finish_executing(self, result):
- """
- After doing some kind of code running in the inferior, print the line
- of source code or the result of the last executed gdb command (passed
- in as the `result` argument).
- """
- output_on_halt, output_always = self.filter_output(result)
-
- if self.stopped():
- print(output_always)
- print(output_on_halt)
- else:
- frame = gdb.selected_frame()
- source_line = self.lang_info.get_source_line(frame)
- if self.lang_info.is_relevant_function(frame):
- raised_exception = self.lang_info.exc_info(frame)
- if raised_exception:
- print(raised_exception)
-
- if source_line:
- if output_always.rstrip():
- print(output_always.rstrip())
- print(source_line)
- else:
- print(result)
-
- def _finish(self):
- """
- Execute until the function returns (or until something else makes it
- stop)
- """
- if gdb.selected_frame().older() is not None:
- return gdb.execute('finish', to_string=True)
- else:
- # outermost frame, continue
- return gdb.execute('cont', to_string=True)
-
- def _finish_frame(self):
- """
- Execute until the function returns to a relevant caller.
- """
- while True:
- result = self._finish()
-
- try:
- frame = gdb.selected_frame()
- except RuntimeError:
- break
-
- hitbp = re.search(r'Breakpoint (\d+)', result)
- is_relevant = self.lang_info.is_relevant_function(frame)
- if hitbp or is_relevant or self.stopped():
- break
-
- return result
-
- def finish(self, *args):
- "Implements the finish command."
- result = self._finish_frame()
- self.finish_executing(result)
-
- def step(self, stepinto, stepover_command='next'):
- """
- Do a single step or step-over. Returns the result of the last gdb
- command that made execution stop.
-
- This implementation, for stepping, sets (conditional) breakpoints for
- all functions that are deemed relevant. It then does a step over until
- either something halts execution, or until the next line is reached.
-
- If, however, stepover_command is given, it should be a string gdb
- command that continues execution in some way. The idea is that the
- caller has set a (conditional) breakpoint or watchpoint that can work
- more efficiently than the step-over loop. For Python this means setting
- a watchpoint for f->f_lasti, which means we can then subsequently
- "finish" frames.
- We want f->f_lasti instead of f->f_lineno, because the latter only
- works properly with local trace functions, see
- PyFrameObjectPtr.current_line_num and PyFrameObjectPtr.addr2line.
- """
- if stepinto:
- breakpoint_list = list(self.install_breakpoints())
-
- beginframe = gdb.selected_frame()
-
- if self.lang_info.is_relevant_function(beginframe):
- # If we start in a relevant frame, initialize stuff properly. If
- # we don't start in a relevant frame, the loop will halt
- # immediately. So don't call self.lang_info.lineno() as it may
- # raise for irrelevant frames.
- beginline = self.lang_info.lineno(beginframe)
-
- if not stepinto:
- depth = stackdepth(beginframe)
-
- newframe = beginframe
-
- while True:
- if self.lang_info.is_relevant_function(newframe):
- result = gdb.execute(stepover_command, to_string=True)
- else:
- result = self._finish_frame()
-
- if self.stopped():
- break
-
- newframe = gdb.selected_frame()
- is_relevant_function = self.lang_info.is_relevant_function(newframe)
- try:
- framename = newframe.name()
- except RuntimeError:
- framename = None
-
- m = re.search(r'Breakpoint (\d+)', result)
- if m:
- if is_relevant_function and m.group(1) in breakpoint_list:
- # although we hit a breakpoint, we still need to check
- # that the function, in case hit by a runtime breakpoint,
- # is in the right context
- break
-
- if newframe != beginframe:
- # new function
-
- if not stepinto:
- # see if we returned to the caller
- newdepth = stackdepth(newframe)
- is_relevant_function = (newdepth < depth and
- is_relevant_function)
-
- if is_relevant_function:
- break
- else:
- # newframe equals beginframe, check for a difference in the
- # line number
- lineno = self.lang_info.lineno(newframe)
- if lineno and lineno != beginline:
- break
-
- if stepinto:
- self.delete_breakpoints(breakpoint_list)
-
- self.finish_executing(result)
-
- def run(self, args, from_tty):
- self.finish_executing(gdb.execute('run ' + args, to_string=True))
-
- def cont(self, *args):
- self.finish_executing(gdb.execute('cont', to_string=True))
-
-
-class LanguageInfo(object):
- """
- This class defines the interface that ExecutionControlCommandBase needs to
- provide language-specific execution control.
-
- Classes that implement this interface should implement:
-
- lineno(frame)
- Tells the current line number (only called for a relevant frame).
- If lineno is a false value it is not checked for a difference.
-
- is_relevant_function(frame)
- tells whether we care about frame 'frame'
-
- get_source_line(frame)
- get the line of source code for the current line (only called for a
- relevant frame). If the source code cannot be retrieved this
- function should return None
-
- exc_info(frame) -- optional
- tells whether an exception was raised, if so, it should return a
- string representation of the exception value, None otherwise.
-
- static_break_functions()
- returns an iterable of function names that are considered relevant
- and should halt step-into execution. This is needed to provide a
- performing step-into
-
- runtime_break_functions() -- optional
- list of functions that we should break into depending on the
- context
- """
-
- def exc_info(self, frame):
- "See this class' docstring."
-
- def runtime_break_functions(self):
- """
- Implement this if the list of step-into functions depends on the
- context.
- """
- return ()
-
-
-class PythonInfo(LanguageInfo):
-
- def pyframe(self, frame):
- pyframe = Frame(frame).get_pyop()
- if pyframe:
- return pyframe
- else:
- raise gdb.RuntimeError(
- "Unable to find the Python frame, run your code with a debug "
- "build (configure with --with-pydebug or compile with -g).")
-
- def lineno(self, frame):
- return self.pyframe(frame).current_line_num()
-
- def is_relevant_function(self, frame):
- return Frame(frame).is_evalframeex()
-
- def get_source_line(self, frame):
- try:
- pyframe = self.pyframe(frame)
- return '%4d %s' % (pyframe.current_line_num(),
- pyframe.current_line().rstrip())
- except IOError:
- return None
-
- def exc_info(self, frame):
- try:
- tstate = frame.read_var('tstate').dereference()
- if gdb.parse_and_eval('tstate->frame == f'):
- # tstate local variable initialized, check for an exception
- inf_type = tstate['curexc_type']
- inf_value = tstate['curexc_value']
-
- if inf_type:
- return 'An exception was raised: %s' % (inf_value,)
- except (ValueError, RuntimeError):
- # Could not read the variable tstate or it's memory, it's ok
- pass
-
- def static_break_functions(self):
- yield 'PyEval_EvalFrameEx'
-
-
-class PythonStepperMixin(object):
- """
- Make this a mixin so CyStep can also inherit from this and use a
- CythonCodeStepper at the same time.
- """
-
- def python_step(self, stepinto):
- """
- Set a watchpoint on the Python bytecode instruction pointer and try
- to finish the frame
- """
- output = gdb.execute('watch f->f_lasti', to_string=True)
- watchpoint = int(re.search(r'[Ww]atchpoint (\d+):', output).group(1))
- self.step(stepinto=stepinto, stepover_command='finish')
- gdb.execute('delete %s' % watchpoint)
-
-
-class PyStep(ExecutionControlCommandBase, PythonStepperMixin):
- "Step through Python code."
-
- stepinto = True
-
- def invoke(self, args, from_tty):
- self.python_step(stepinto=self.stepinto)
-
-
-class PyNext(PyStep):
- "Step-over Python code."
-
- stepinto = False
-
-
-class PyFinish(ExecutionControlCommandBase):
- "Execute until function returns to a caller."
-
- invoke = ExecutionControlCommandBase.finish
-
-
-class PyRun(ExecutionControlCommandBase):
- "Run the program."
-
- invoke = ExecutionControlCommandBase.run
-
-
-class PyCont(ExecutionControlCommandBase):
-
- invoke = ExecutionControlCommandBase.cont
-
-
-def _pointervalue(gdbval):
- """
+ _execute("set logging file %s" % self.filename)
+ self.file_position_stack = []
+
+ def __enter__(self):
+ if not self.file_position_stack:
+ _execute("set logging redirect on")
+ _execute("set logging on")
+ _execute("set pagination off")
+
+ self.file_position_stack.append(os.fstat(self.fd).st_size)
+ return self
+
+ def getoutput(self):
+ gdb.flush()
+ self.file.seek(self.file_position_stack[-1])
+ result = self.file.read()
+ return result
+
+ def __exit__(self, exc_type, exc_val, tb):
+ startpos = self.file_position_stack.pop()
+ self.file.seek(startpos)
+ self.file.truncate()
+ if not self.file_position_stack:
+ _execute("set logging off")
+ _execute("set logging redirect off")
+ _execute("set pagination on")
+
+
+def execute(command, from_tty=False, to_string=False):
+ """
+ Replace gdb.execute() with this function and have it accept a 'to_string'
+ argument (new in 7.2). Have it properly capture stderr also. Ensure
+ reentrancy.
+ """
+ if to_string:
+ with _logging_state as state:
+ _execute(command, from_tty)
+ return state.getoutput()
+ else:
+ _execute(command, from_tty)
+
+
+_execute = gdb.execute
+gdb.execute = execute
+_logging_state = _LoggingState()
+
+
+def get_selected_inferior():
+ """
+ Return the selected inferior in gdb.
+ """
+ # Woooh, another bug in gdb! Is there an end in sight?
+ # http://sourceware.org/bugzilla/show_bug.cgi?id=12212
+ return gdb.inferiors()[0]
+
+ selected_thread = gdb.selected_thread()
+
+ for inferior in gdb.inferiors():
+ for thread in inferior.threads():
+ if thread == selected_thread:
+ return inferior
+
+
+def source_gdb_script(script_contents, to_string=False):
+ """
+ Source a gdb script with script_contents passed as a string. This is useful
+ to provide defines for py-step and py-next to make them repeatable (this is
+ not possible with gdb.execute()). See
+ http://sourceware.org/bugzilla/show_bug.cgi?id=12216
+ """
+ fd, filename = tempfile.mkstemp()
+ f = os.fdopen(fd, 'w')
+ f.write(script_contents)
+ f.close()
+ gdb.execute("source %s" % filename, to_string=to_string)
+ os.remove(filename)
+
+
+def register_defines():
+ source_gdb_script(textwrap.dedent("""\
+ define py-step
+ -py-step
+ end
+
+ define py-next
+ -py-next
+ end
+
+ document py-step
+ %s
+ end
+
+ document py-next
+ %s
+ end
+ """) % (PyStep.__doc__, PyNext.__doc__))
+
+
+def stackdepth(frame):
+ "Tells the stackdepth of a gdb frame."
+ depth = 0
+ while frame:
+ frame = frame.older()
+ depth += 1
+
+ return depth
+
+
+class ExecutionControlCommandBase(gdb.Command):
+ """
+ Superclass for language specific execution control. Language specific
+ features should be implemented by lang_info using the LanguageInfo
+ interface. 'name' is the name of the command.
+ """
+
+ def __init__(self, name, lang_info):
+ super(ExecutionControlCommandBase, self).__init__(
+ name, gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE)
+ self.lang_info = lang_info
+
+ def install_breakpoints(self):
+ all_locations = itertools.chain(
+ self.lang_info.static_break_functions(),
+ self.lang_info.runtime_break_functions())
+
+ for location in all_locations:
+ result = gdb.execute('break %s' % location, to_string=True)
+ yield re.search(r'Breakpoint (\d+)', result).group(1)
+
+ def delete_breakpoints(self, breakpoint_list):
+ for bp in breakpoint_list:
+ gdb.execute("delete %s" % bp)
+
+ def filter_output(self, result):
+ reflags = re.MULTILINE
+
+ output_on_halt = [
+ (r'^Program received signal .*', reflags|re.DOTALL),
+ (r'.*[Ww]arning.*', 0),
+ (r'^Program exited .*', reflags),
+ ]
+
+ output_always = [
+ # output when halting on a watchpoint
+ (r'^(Old|New) value = .*', reflags),
+ # output from the 'display' command
+ (r'^\d+: \w+ = .*', reflags),
+ ]
+
+ def filter_output(regexes):
+ output = []
+ for regex, flags in regexes:
+ for match in re.finditer(regex, result, flags):
+ output.append(match.group(0))
+
+ return '\n'.join(output)
+
+ # Filter the return value output of the 'finish' command
+ match_finish = re.search(r'^Value returned is \$\d+ = (.*)', result,
+ re.MULTILINE)
+ if match_finish:
+ finish_output = 'Value returned: %s\n' % match_finish.group(1)
+ else:
+ finish_output = ''
+
+ return (filter_output(output_on_halt),
+ finish_output + filter_output(output_always))
+
+ def stopped(self):
+ return get_selected_inferior().pid == 0
+
+ def finish_executing(self, result):
+ """
+ After doing some kind of code running in the inferior, print the line
+ of source code or the result of the last executed gdb command (passed
+ in as the `result` argument).
+ """
+ output_on_halt, output_always = self.filter_output(result)
+
+ if self.stopped():
+ print(output_always)
+ print(output_on_halt)
+ else:
+ frame = gdb.selected_frame()
+ source_line = self.lang_info.get_source_line(frame)
+ if self.lang_info.is_relevant_function(frame):
+ raised_exception = self.lang_info.exc_info(frame)
+ if raised_exception:
+ print(raised_exception)
+
+ if source_line:
+ if output_always.rstrip():
+ print(output_always.rstrip())
+ print(source_line)
+ else:
+ print(result)
+
+ def _finish(self):
+ """
+ Execute until the function returns (or until something else makes it
+ stop)
+ """
+ if gdb.selected_frame().older() is not None:
+ return gdb.execute('finish', to_string=True)
+ else:
+ # outermost frame, continue
+ return gdb.execute('cont', to_string=True)
+
+ def _finish_frame(self):
+ """
+ Execute until the function returns to a relevant caller.
+ """
+ while True:
+ result = self._finish()
+
+ try:
+ frame = gdb.selected_frame()
+ except RuntimeError:
+ break
+
+ hitbp = re.search(r'Breakpoint (\d+)', result)
+ is_relevant = self.lang_info.is_relevant_function(frame)
+ if hitbp or is_relevant or self.stopped():
+ break
+
+ return result
+
+ def finish(self, *args):
+ "Implements the finish command."
+ result = self._finish_frame()
+ self.finish_executing(result)
+
+ def step(self, stepinto, stepover_command='next'):
+ """
+ Do a single step or step-over. Returns the result of the last gdb
+ command that made execution stop.
+
+ This implementation, for stepping, sets (conditional) breakpoints for
+ all functions that are deemed relevant. It then does a step over until
+ either something halts execution, or until the next line is reached.
+
+ If, however, stepover_command is given, it should be a string gdb
+ command that continues execution in some way. The idea is that the
+ caller has set a (conditional) breakpoint or watchpoint that can work
+ more efficiently than the step-over loop. For Python this means setting
+ a watchpoint for f->f_lasti, which means we can then subsequently
+ "finish" frames.
+ We want f->f_lasti instead of f->f_lineno, because the latter only
+ works properly with local trace functions, see
+ PyFrameObjectPtr.current_line_num and PyFrameObjectPtr.addr2line.
+ """
+ if stepinto:
+ breakpoint_list = list(self.install_breakpoints())
+
+ beginframe = gdb.selected_frame()
+
+ if self.lang_info.is_relevant_function(beginframe):
+ # If we start in a relevant frame, initialize stuff properly. If
+ # we don't start in a relevant frame, the loop will halt
+ # immediately. So don't call self.lang_info.lineno() as it may
+ # raise for irrelevant frames.
+ beginline = self.lang_info.lineno(beginframe)
+
+ if not stepinto:
+ depth = stackdepth(beginframe)
+
+ newframe = beginframe
+
+ while True:
+ if self.lang_info.is_relevant_function(newframe):
+ result = gdb.execute(stepover_command, to_string=True)
+ else:
+ result = self._finish_frame()
+
+ if self.stopped():
+ break
+
+ newframe = gdb.selected_frame()
+ is_relevant_function = self.lang_info.is_relevant_function(newframe)
+ try:
+ framename = newframe.name()
+ except RuntimeError:
+ framename = None
+
+ m = re.search(r'Breakpoint (\d+)', result)
+ if m:
+ if is_relevant_function and m.group(1) in breakpoint_list:
+ # although we hit a breakpoint, we still need to check
+ # that the function, in case hit by a runtime breakpoint,
+ # is in the right context
+ break
+
+ if newframe != beginframe:
+ # new function
+
+ if not stepinto:
+ # see if we returned to the caller
+ newdepth = stackdepth(newframe)
+ is_relevant_function = (newdepth < depth and
+ is_relevant_function)
+
+ if is_relevant_function:
+ break
+ else:
+ # newframe equals beginframe, check for a difference in the
+ # line number
+ lineno = self.lang_info.lineno(newframe)
+ if lineno and lineno != beginline:
+ break
+
+ if stepinto:
+ self.delete_breakpoints(breakpoint_list)
+
+ self.finish_executing(result)
+
+ def run(self, args, from_tty):
+ self.finish_executing(gdb.execute('run ' + args, to_string=True))
+
+ def cont(self, *args):
+ self.finish_executing(gdb.execute('cont', to_string=True))
+
+
+class LanguageInfo(object):
+ """
+ This class defines the interface that ExecutionControlCommandBase needs to
+ provide language-specific execution control.
+
+ Classes that implement this interface should implement:
+
+ lineno(frame)
+ Tells the current line number (only called for a relevant frame).
+ If lineno is a false value it is not checked for a difference.
+
+ is_relevant_function(frame)
+ tells whether we care about frame 'frame'
+
+ get_source_line(frame)
+ get the line of source code for the current line (only called for a
+ relevant frame). If the source code cannot be retrieved this
+ function should return None
+
+ exc_info(frame) -- optional
+ tells whether an exception was raised, if so, it should return a
+ string representation of the exception value, None otherwise.
+
+ static_break_functions()
+ returns an iterable of function names that are considered relevant
+ and should halt step-into execution. This is needed to provide a
+ performing step-into
+
+ runtime_break_functions() -- optional
+ list of functions that we should break into depending on the
+ context
+ """
+
+ def exc_info(self, frame):
+ "See this class' docstring."
+
+ def runtime_break_functions(self):
+ """
+ Implement this if the list of step-into functions depends on the
+ context.
+ """
+ return ()
+
+
+class PythonInfo(LanguageInfo):
+
+ def pyframe(self, frame):
+ pyframe = Frame(frame).get_pyop()
+ if pyframe:
+ return pyframe
+ else:
+ raise gdb.RuntimeError(
+ "Unable to find the Python frame, run your code with a debug "
+ "build (configure with --with-pydebug or compile with -g).")
+
+ def lineno(self, frame):
+ return self.pyframe(frame).current_line_num()
+
+ def is_relevant_function(self, frame):
+ return Frame(frame).is_evalframeex()
+
+ def get_source_line(self, frame):
+ try:
+ pyframe = self.pyframe(frame)
+ return '%4d %s' % (pyframe.current_line_num(),
+ pyframe.current_line().rstrip())
+ except IOError:
+ return None
+
+ def exc_info(self, frame):
+ try:
+ tstate = frame.read_var('tstate').dereference()
+ if gdb.parse_and_eval('tstate->frame == f'):
+ # tstate local variable initialized, check for an exception
+ inf_type = tstate['curexc_type']
+ inf_value = tstate['curexc_value']
+
+ if inf_type:
+ return 'An exception was raised: %s' % (inf_value,)
+ except (ValueError, RuntimeError):
+ # Could not read the variable tstate or it's memory, it's ok
+ pass
+
+ def static_break_functions(self):
+ yield 'PyEval_EvalFrameEx'
+
+
+class PythonStepperMixin(object):
+ """
+ Make this a mixin so CyStep can also inherit from this and use a
+ CythonCodeStepper at the same time.
+ """
+
+ def python_step(self, stepinto):
+ """
+ Set a watchpoint on the Python bytecode instruction pointer and try
+ to finish the frame
+ """
+ output = gdb.execute('watch f->f_lasti', to_string=True)
+ watchpoint = int(re.search(r'[Ww]atchpoint (\d+):', output).group(1))
+ self.step(stepinto=stepinto, stepover_command='finish')
+ gdb.execute('delete %s' % watchpoint)
+
+
+class PyStep(ExecutionControlCommandBase, PythonStepperMixin):
+ "Step through Python code."
+
+ stepinto = True
+
+ def invoke(self, args, from_tty):
+ self.python_step(stepinto=self.stepinto)
+
+
+class PyNext(PyStep):
+ "Step-over Python code."
+
+ stepinto = False
+
+
+class PyFinish(ExecutionControlCommandBase):
+ "Execute until function returns to a caller."
+
+ invoke = ExecutionControlCommandBase.finish
+
+
+class PyRun(ExecutionControlCommandBase):
+ "Run the program."
+
+ invoke = ExecutionControlCommandBase.run
+
+
+class PyCont(ExecutionControlCommandBase):
+
+ invoke = ExecutionControlCommandBase.cont
+
+
+def _pointervalue(gdbval):
+ """
Return the value of the pointer as a Python int.
-
- gdbval.type must be a pointer type
- """
- # don't convert with int() as it will raise a RuntimeError
- if gdbval.address is not None:
+
+ gdbval.type must be a pointer type
+ """
+ # don't convert with int() as it will raise a RuntimeError
+ if gdbval.address is not None:
return int(gdbval.address)
- else:
- # the address attribute is None sometimes, in which case we can
- # still convert the pointer to an int
+ else:
+ # the address attribute is None sometimes, in which case we can
+ # still convert the pointer to an int
return int(gdbval)
-
-
-def pointervalue(gdbval):
- pointer = _pointervalue(gdbval)
- try:
- if pointer < 0:
- raise gdb.GdbError("Negative pointer value, presumably a bug "
- "in gdb, aborting.")
- except RuntimeError:
- # work around yet another bug in gdb where you get random behaviour
- # and tracebacks
- pass
-
- return pointer
-
-
-def get_inferior_unicode_postfix():
- try:
- gdb.parse_and_eval('PyUnicode_FromEncodedObject')
- except RuntimeError:
- try:
- gdb.parse_and_eval('PyUnicodeUCS2_FromEncodedObject')
- except RuntimeError:
- return 'UCS4'
- else:
- return 'UCS2'
- else:
- return ''
-
-
-class PythonCodeExecutor(object):
-
- Py_single_input = 256
- Py_file_input = 257
- Py_eval_input = 258
-
- def malloc(self, size):
- chunk = (gdb.parse_and_eval("(void *) malloc((size_t) %d)" % size))
-
- pointer = pointervalue(chunk)
- if pointer == 0:
- raise gdb.GdbError("No memory could be allocated in the inferior.")
-
- return pointer
-
- def alloc_string(self, string):
- pointer = self.malloc(len(string))
- get_selected_inferior().write_memory(pointer, string)
-
- return pointer
-
- def alloc_pystring(self, string):
- stringp = self.alloc_string(string)
- PyString_FromStringAndSize = 'PyString_FromStringAndSize'
-
- try:
- gdb.parse_and_eval(PyString_FromStringAndSize)
- except RuntimeError:
- # Python 3
- PyString_FromStringAndSize = ('PyUnicode%s_FromStringAndSize' %
- (get_inferior_unicode_postfix(),))
-
- try:
- result = gdb.parse_and_eval(
- '(PyObject *) %s((char *) %d, (size_t) %d)' % (
- PyString_FromStringAndSize, stringp, len(string)))
- finally:
- self.free(stringp)
-
- pointer = pointervalue(result)
- if pointer == 0:
- raise gdb.GdbError("Unable to allocate Python string in "
- "the inferior.")
-
- return pointer
-
- def free(self, pointer):
- gdb.parse_and_eval("free((void *) %d)" % pointer)
-
- def incref(self, pointer):
- "Increment the reference count of a Python object in the inferior."
- gdb.parse_and_eval('Py_IncRef((PyObject *) %d)' % pointer)
-
- def xdecref(self, pointer):
- "Decrement the reference count of a Python object in the inferior."
- # Py_DecRef is like Py_XDECREF, but a function. So we don't have
- # to check for NULL. This should also decref all our allocated
- # Python strings.
- gdb.parse_and_eval('Py_DecRef((PyObject *) %d)' % pointer)
-
- def evalcode(self, code, input_type, global_dict=None, local_dict=None):
- """
- Evaluate python code `code` given as a string in the inferior and
- return the result as a gdb.Value. Returns a new reference in the
- inferior.
-
- Of course, executing any code in the inferior may be dangerous and may
+
+
+def pointervalue(gdbval):
+ pointer = _pointervalue(gdbval)
+ try:
+ if pointer < 0:
+ raise gdb.GdbError("Negative pointer value, presumably a bug "
+ "in gdb, aborting.")
+ except RuntimeError:
+ # work around yet another bug in gdb where you get random behaviour
+ # and tracebacks
+ pass
+
+ return pointer
+
+
+def get_inferior_unicode_postfix():
+ try:
+ gdb.parse_and_eval('PyUnicode_FromEncodedObject')
+ except RuntimeError:
+ try:
+ gdb.parse_and_eval('PyUnicodeUCS2_FromEncodedObject')
+ except RuntimeError:
+ return 'UCS4'
+ else:
+ return 'UCS2'
+ else:
+ return ''
+
+
+class PythonCodeExecutor(object):
+
+ Py_single_input = 256
+ Py_file_input = 257
+ Py_eval_input = 258
+
+ def malloc(self, size):
+ chunk = (gdb.parse_and_eval("(void *) malloc((size_t) %d)" % size))
+
+ pointer = pointervalue(chunk)
+ if pointer == 0:
+ raise gdb.GdbError("No memory could be allocated in the inferior.")
+
+ return pointer
+
+ def alloc_string(self, string):
+ pointer = self.malloc(len(string))
+ get_selected_inferior().write_memory(pointer, string)
+
+ return pointer
+
+ def alloc_pystring(self, string):
+ stringp = self.alloc_string(string)
+ PyString_FromStringAndSize = 'PyString_FromStringAndSize'
+
+ try:
+ gdb.parse_and_eval(PyString_FromStringAndSize)
+ except RuntimeError:
+ # Python 3
+ PyString_FromStringAndSize = ('PyUnicode%s_FromStringAndSize' %
+ (get_inferior_unicode_postfix(),))
+
+ try:
+ result = gdb.parse_and_eval(
+ '(PyObject *) %s((char *) %d, (size_t) %d)' % (
+ PyString_FromStringAndSize, stringp, len(string)))
+ finally:
+ self.free(stringp)
+
+ pointer = pointervalue(result)
+ if pointer == 0:
+ raise gdb.GdbError("Unable to allocate Python string in "
+ "the inferior.")
+
+ return pointer
+
+ def free(self, pointer):
+ gdb.parse_and_eval("free((void *) %d)" % pointer)
+
+ def incref(self, pointer):
+ "Increment the reference count of a Python object in the inferior."
+ gdb.parse_and_eval('Py_IncRef((PyObject *) %d)' % pointer)
+
+ def xdecref(self, pointer):
+ "Decrement the reference count of a Python object in the inferior."
+ # Py_DecRef is like Py_XDECREF, but a function. So we don't have
+ # to check for NULL. This should also decref all our allocated
+ # Python strings.
+ gdb.parse_and_eval('Py_DecRef((PyObject *) %d)' % pointer)
+
+ def evalcode(self, code, input_type, global_dict=None, local_dict=None):
+ """
+ Evaluate python code `code` given as a string in the inferior and
+ return the result as a gdb.Value. Returns a new reference in the
+ inferior.
+
+ Of course, executing any code in the inferior may be dangerous and may
leave the debuggee in an unsafe state or terminate it altogether.
- """
- if '\0' in code:
- raise gdb.GdbError("String contains NUL byte.")
-
- code += '\0'
-
- pointer = self.alloc_string(code)
-
- globalsp = pointervalue(global_dict)
- localsp = pointervalue(local_dict)
-
- if globalsp == 0 or localsp == 0:
- raise gdb.GdbError("Unable to obtain or create locals or globals.")
-
- code = """
- PyRun_String(
- (char *) %(code)d,
- (int) %(start)d,
- (PyObject *) %(globals)s,
- (PyObject *) %(locals)d)
- """ % dict(code=pointer, start=input_type,
- globals=globalsp, locals=localsp)
-
- with FetchAndRestoreError():
- try:
- pyobject_return_value = gdb.parse_and_eval(code)
- finally:
- self.free(pointer)
-
- return pyobject_return_value
-
-
-class FetchAndRestoreError(PythonCodeExecutor):
- """
- Context manager that fetches the error indicator in the inferior and
- restores it on exit.
- """
-
- def __init__(self):
- self.sizeof_PyObjectPtr = gdb.lookup_type('PyObject').pointer().sizeof
- self.pointer = self.malloc(self.sizeof_PyObjectPtr * 3)
-
- type = self.pointer
- value = self.pointer + self.sizeof_PyObjectPtr
- traceback = self.pointer + self.sizeof_PyObjectPtr * 2
-
- self.errstate = type, value, traceback
-
- def __enter__(self):
- gdb.parse_and_eval("PyErr_Fetch(%d, %d, %d)" % self.errstate)
-
- def __exit__(self, *args):
- if gdb.parse_and_eval("(int) PyErr_Occurred()"):
- gdb.parse_and_eval("PyErr_Print()")
-
- pyerr_restore = ("PyErr_Restore("
- "(PyObject *) *%d,"
- "(PyObject *) *%d,"
- "(PyObject *) *%d)")
-
- try:
- gdb.parse_and_eval(pyerr_restore % self.errstate)
- finally:
- self.free(self.pointer)
-
-
-class FixGdbCommand(gdb.Command):
-
- def __init__(self, command, actual_command):
- super(FixGdbCommand, self).__init__(command, gdb.COMMAND_DATA,
- gdb.COMPLETE_NONE)
- self.actual_command = actual_command
-
- def fix_gdb(self):
- """
+ """
+ if '\0' in code:
+ raise gdb.GdbError("String contains NUL byte.")
+
+ code += '\0'
+
+ pointer = self.alloc_string(code)
+
+ globalsp = pointervalue(global_dict)
+ localsp = pointervalue(local_dict)
+
+ if globalsp == 0 or localsp == 0:
+ raise gdb.GdbError("Unable to obtain or create locals or globals.")
+
+ code = """
+ PyRun_String(
+ (char *) %(code)d,
+ (int) %(start)d,
+ (PyObject *) %(globals)s,
+ (PyObject *) %(locals)d)
+ """ % dict(code=pointer, start=input_type,
+ globals=globalsp, locals=localsp)
+
+ with FetchAndRestoreError():
+ try:
+ pyobject_return_value = gdb.parse_and_eval(code)
+ finally:
+ self.free(pointer)
+
+ return pyobject_return_value
+
+
+class FetchAndRestoreError(PythonCodeExecutor):
+ """
+ Context manager that fetches the error indicator in the inferior and
+ restores it on exit.
+ """
+
+ def __init__(self):
+ self.sizeof_PyObjectPtr = gdb.lookup_type('PyObject').pointer().sizeof
+ self.pointer = self.malloc(self.sizeof_PyObjectPtr * 3)
+
+ type = self.pointer
+ value = self.pointer + self.sizeof_PyObjectPtr
+ traceback = self.pointer + self.sizeof_PyObjectPtr * 2
+
+ self.errstate = type, value, traceback
+
+ def __enter__(self):
+ gdb.parse_and_eval("PyErr_Fetch(%d, %d, %d)" % self.errstate)
+
+ def __exit__(self, *args):
+ if gdb.parse_and_eval("(int) PyErr_Occurred()"):
+ gdb.parse_and_eval("PyErr_Print()")
+
+ pyerr_restore = ("PyErr_Restore("
+ "(PyObject *) *%d,"
+ "(PyObject *) *%d,"
+ "(PyObject *) *%d)")
+
+ try:
+ gdb.parse_and_eval(pyerr_restore % self.errstate)
+ finally:
+ self.free(self.pointer)
+
+
+class FixGdbCommand(gdb.Command):
+
+ def __init__(self, command, actual_command):
+ super(FixGdbCommand, self).__init__(command, gdb.COMMAND_DATA,
+ gdb.COMPLETE_NONE)
+ self.actual_command = actual_command
+
+ def fix_gdb(self):
+ """
It seems that invoking either 'cy exec' and 'py-exec' work perfectly
fine, but after this gdb's python API is entirely broken.
- Maybe some uncleared exception value is still set?
- sys.exc_clear() didn't help. A demonstration:
-
- (gdb) cy exec 'hello'
- 'hello'
- (gdb) python gdb.execute('cont')
- RuntimeError: Cannot convert value to int.
- Error while executing Python code.
- (gdb) python gdb.execute('cont')
- [15148 refs]
-
- Program exited normally.
- """
- warnings.filterwarnings('ignore', r'.*', RuntimeWarning,
- re.escape(__name__))
- try:
+ Maybe some uncleared exception value is still set?
+ sys.exc_clear() didn't help. A demonstration:
+
+ (gdb) cy exec 'hello'
+ 'hello'
+ (gdb) python gdb.execute('cont')
+ RuntimeError: Cannot convert value to int.
+ Error while executing Python code.
+ (gdb) python gdb.execute('cont')
+ [15148 refs]
+
+ Program exited normally.
+ """
+ warnings.filterwarnings('ignore', r'.*', RuntimeWarning,
+ re.escape(__name__))
+ try:
int(gdb.parse_and_eval("(void *) 0")) == 0
- except RuntimeError:
- pass
- # warnings.resetwarnings()
-
- def invoke(self, args, from_tty):
- self.fix_gdb()
- try:
- gdb.execute('%s %s' % (self.actual_command, args))
- except RuntimeError as e:
- raise gdb.GdbError(str(e))
- self.fix_gdb()
-
-
-def _evalcode_python(executor, code, input_type):
- """
- Execute Python code in the most recent stack frame.
- """
- global_dict = gdb.parse_and_eval('PyEval_GetGlobals()')
- local_dict = gdb.parse_and_eval('PyEval_GetLocals()')
-
- if (pointervalue(global_dict) == 0 or pointervalue(local_dict) == 0):
- raise gdb.GdbError("Unable to find the locals or globals of the "
- "most recent Python function (relative to the "
- "selected frame).")
-
- return executor.evalcode(code, input_type, global_dict, local_dict)
-
-
-class PyExec(gdb.Command):
-
- def readcode(self, expr):
- if expr:
- return expr, PythonCodeExecutor.Py_single_input
- else:
- lines = []
- while True:
- try:
+ except RuntimeError:
+ pass
+ # warnings.resetwarnings()
+
+ def invoke(self, args, from_tty):
+ self.fix_gdb()
+ try:
+ gdb.execute('%s %s' % (self.actual_command, args))
+ except RuntimeError as e:
+ raise gdb.GdbError(str(e))
+ self.fix_gdb()
+
+
+def _evalcode_python(executor, code, input_type):
+ """
+ Execute Python code in the most recent stack frame.
+ """
+ global_dict = gdb.parse_and_eval('PyEval_GetGlobals()')
+ local_dict = gdb.parse_and_eval('PyEval_GetLocals()')
+
+ if (pointervalue(global_dict) == 0 or pointervalue(local_dict) == 0):
+ raise gdb.GdbError("Unable to find the locals or globals of the "
+ "most recent Python function (relative to the "
+ "selected frame).")
+
+ return executor.evalcode(code, input_type, global_dict, local_dict)
+
+
+class PyExec(gdb.Command):
+
+ def readcode(self, expr):
+ if expr:
+ return expr, PythonCodeExecutor.Py_single_input
+ else:
+ lines = []
+ while True:
+ try:
line = input('>')
- except EOFError:
- break
- else:
- if line.rstrip() == 'end':
- break
-
- lines.append(line)
-
- return '\n'.join(lines), PythonCodeExecutor.Py_file_input
-
- def invoke(self, expr, from_tty):
- expr, input_type = self.readcode(expr)
- executor = PythonCodeExecutor()
- executor.xdecref(_evalcode_python(executor, input_type, global_dict, local_dict))
-
-
-gdb.execute('set breakpoint pending on')
-
-if hasattr(gdb, 'GdbError'):
- # Wrap py-step and py-next in gdb defines to make them repeatable.
- py_step = PyStep('-py-step', PythonInfo())
- py_next = PyNext('-py-next', PythonInfo())
- register_defines()
- py_finish = PyFinish('py-finish', PythonInfo())
- py_run = PyRun('py-run', PythonInfo())
- py_cont = PyCont('py-cont', PythonInfo())
-
- py_exec = FixGdbCommand('py-exec', '-py-exec')
- _py_exec = PyExec("-py-exec", gdb.COMMAND_DATA, gdb.COMPLETE_NONE)
-else:
- warnings.warn("Use gdb 7.2 or higher to use the py-exec command.")
+ except EOFError:
+ break
+ else:
+ if line.rstrip() == 'end':
+ break
+
+ lines.append(line)
+
+ return '\n'.join(lines), PythonCodeExecutor.Py_file_input
+
+ def invoke(self, expr, from_tty):
+ expr, input_type = self.readcode(expr)
+ executor = PythonCodeExecutor()
+ executor.xdecref(_evalcode_python(executor, input_type, global_dict, local_dict))
+
+
+gdb.execute('set breakpoint pending on')
+
+if hasattr(gdb, 'GdbError'):
+ # Wrap py-step and py-next in gdb defines to make them repeatable.
+ py_step = PyStep('-py-step', PythonInfo())
+ py_next = PyNext('-py-next', PythonInfo())
+ register_defines()
+ py_finish = PyFinish('py-finish', PythonInfo())
+ py_run = PyRun('py-run', PythonInfo())
+ py_cont = PyCont('py-cont', PythonInfo())
+
+ py_exec = FixGdbCommand('py-exec', '-py-exec')
+ _py_exec = PyExec("-py-exec", gdb.COMMAND_DATA, gdb.COMPLETE_NONE)
+else:
+ warnings.warn("Use gdb 7.2 or higher to use the py-exec command.")