Index: Python/sysmodule.c =================================================================== --- Python/sysmodule.c (revision 86100) +++ Python/sysmodule.c (working copy) @@ -67,7 +67,25 @@ return PyDict_SetItemString(sd, name, v); } +#ifdef Py_BREAKPOINT +PyDoc_STRVAR(breakpoint_doc, +"_breakpoint(args) -> None\n" +"\n" +"Inject a breakpoint signal in this process, for use when running CPython\n" +"under a debugger.\n" +"Note that invoking this when running outside of a C-level debugger is likely\n" +"to make the process terminate\n" +); + static PyObject * +sys_breakpoint(PyObject *self, PyObject *args) +{ + Py_BREAKPOINT(); + Py_RETURN_NONE; +} +#endif /* Py_BREAKPOINT */ + +static PyObject * sys_displayhook(PyObject *self, PyObject *o) { PyObject *outf; @@ -949,6 +967,9 @@ static PyMethodDef sys_methods[] = { /* Might as well keep this in alphabetic order */ +#ifdef Py_BREAKPOINT + {"_breakpoint", sys_breakpoint, METH_VARARGS, breakpoint_doc}, +#endif {"callstats", (PyCFunction)PyEval_GetCallStats, METH_NOARGS, callstats_doc}, {"_clear_type_cache", sys_clear_type_cache, METH_NOARGS, Index: Include/pyport.h =================================================================== --- Include/pyport.h (revision 86100) +++ Include/pyport.h (working copy) @@ -836,4 +836,13 @@ #endif #endif +/* + * System-dependent ways of injecting a breakpoint: + */ +#if defined(MS_WINDOWS) && defined(_DEBUG) +# define Py_BREAKPOINT() DebugBreak() +#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64)) +# define Py_BREAKPOINT() __asm__ __volatile__ ("int $03") +#endif + #endif /* Py_PYPORT_H */ Index: Doc/library/sys.rst =================================================================== --- Doc/library/sys.rst (revision 86100) +++ Doc/library/sys.rst (working copy) @@ -29,6 +29,31 @@ command line, see the :mod:`fileinput` module. +.. function:: _breakpoint(args) + + .. impl-detail:: + This function is only meaningful for the CPython implementation of Python + and thus not likely to be implemented elsewhere. It is only present on + builds of CPython in which the necessary system-dependent hooks are + available. + + This function is for use when debugging CPython (and extension modules). + When running CPython under a C debugger, it injects a C-level breakpoint, + stopping execution at this point. *args* can be arbitrary user-supplied + objects, which should be viewable within the debugger. + + This may be of use when tracking down bugs: the breakpoint can be guarded + by Python-level conditionals:: + + if foo and bar and not baz: + sys._breakpoint(some_func(foo, bar, baz)) + + When running CPython outside of a debugger, this will typically terminate + the process immediately, so remember not to leave these breakpoints in your + final code. + + .. versionadded:: 3.2 + .. data:: byteorder An indicator of the native byte order. This will have the value ``'big'`` on Index: Lib/test/test_sys.py =================================================================== --- Lib/test/test_sys.py (revision 86100) +++ Lib/test/test_sys.py (working copy) @@ -862,8 +862,37 @@ check(sys.flags, size(vh) + self.P * len(sys.flags)) +class BreakpointTest(unittest.TestCase): + def test_sys_breakpoint(self): + try: + gdb_version, _ = subprocess.Popen(["gdb", "--version"], + stdout=subprocess.PIPE).communicate() + except OSError: + # This is what "no gdb" looks like. There may, however, be other + # errors that manifest this way too. + raise unittest.SkipTest("Couldn't find gdb on the path") + + # Run "sys._breakpoint" under gdb in a subprocess: + commands = ['run', 'bt', 'print args'] + args = ["gdb", "--batch"] + args += ['--eval-command=%s' % cmd for cmd in commands] + args += ["--args", + sys.executable, + '-c', + 'import sys; sys._breakpoint(42)'] + out, err = subprocess.Popen( + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + ).communicate() + out = out.decode('utf-8', 'replace') + err = err.decode('utf-8', 'replace') + self.assertEquals(err, '') + self.assertIn('Program received signal SIGTRAP, ' + 'Trace/breakpoint trap.', out) + self.assertIn('sys_breakpoint', out) + self.assertIn('Python/sysmodule.c', out) + def test_main(): - test.support.run_unittest(SysModuleTest, SizeofTest) + test.support.run_unittest(SysModuleTest, SizeofTest, BreakpointTest) if __name__ == "__main__": test_main()