[Python-checkins] bpo-45256: Remove the usage of the C stack in Python to Python calls (GH-28488)
pablogsal
webhook-mailer at python.org
Sat Oct 9 11:51:35 EDT 2021
https://github.com/python/cpython/commit/b4903afd4debbbd71dc49a2c8fefa74a3b6c6832
commit: b4903afd4debbbd71dc49a2c8fefa74a3b6c6832
branch: main
author: Pablo Galindo Salgado <Pablogsal at gmail.com>
committer: pablogsal <Pablogsal at gmail.com>
date: 2021-10-09T16:51:30+01:00
summary:
bpo-45256: Remove the usage of the C stack in Python to Python calls (GH-28488)
Ths commit inlines calls to Python functions in the eval loop and steals all the arguments in the call from the caller for
performance.
files:
M Include/internal/pycore_frame.h
M Include/internal/pycore_tuple.h
M Lib/test/gdb_sample.py
M Lib/test/test_gdb.py
M Objects/tupleobject.c
M Python/ceval.c
M Tools/gdb/libpython.py
diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h
index 489e115a8645c..7e63f584eb3b0 100644
--- a/Include/internal/pycore_frame.h
+++ b/Include/internal/pycore_frame.h
@@ -31,6 +31,7 @@ typedef struct _interpreter_frame {
int f_lasti; /* Last instruction if called */
int stacktop; /* Offset of TOS from localsplus */
PyFrameState f_state; /* What state the frame is in */
+ int depth; /* Depth of the frame in a ceval loop */
PyObject *localsplus[1];
} InterpreterFrame;
@@ -85,6 +86,7 @@ _PyFrame_InitializeSpecials(
frame->generator = NULL;
frame->f_lasti = -1;
frame->f_state = FRAME_CREATED;
+ frame->depth = 0;
}
/* Gets the pointer to the locals array
diff --git a/Include/internal/pycore_tuple.h b/Include/internal/pycore_tuple.h
index d1d0d2a92e49f..79c827fe8800a 100644
--- a/Include/internal/pycore_tuple.h
+++ b/Include/internal/pycore_tuple.h
@@ -13,6 +13,7 @@ extern "C" {
#define _PyTuple_ITEMS(op) (_PyTuple_CAST(op)->ob_item)
extern PyObject *_PyTuple_FromArray(PyObject *const *, Py_ssize_t);
+extern PyObject *_PyTuple_FromArraySteal(PyObject *const *, Py_ssize_t);
#ifdef __cplusplus
}
diff --git a/Lib/test/gdb_sample.py b/Lib/test/gdb_sample.py
index cab13fb4da594..4188f50136fb9 100644
--- a/Lib/test/gdb_sample.py
+++ b/Lib/test/gdb_sample.py
@@ -1,7 +1,7 @@
# Sample script for use by test_gdb.py
def foo(a, b, c):
- bar(a, b, c)
+ bar(a=a, b=b, c=c)
def bar(a, b, c):
baz(a, b, c)
diff --git a/Lib/test/test_gdb.py b/Lib/test/test_gdb.py
index 22a8cf3c25fc0..fb0f1295574b9 100644
--- a/Lib/test/test_gdb.py
+++ b/Lib/test/test_gdb.py
@@ -734,8 +734,14 @@ def test_pyup_command(self):
cmds_after_breakpoint=['py-up', 'py-up'])
self.assertMultilineMatches(bt,
r'''^.*
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 10, in baz \(args=\(1, 2, 3\)\)
+ id\(42\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
baz\(a, b, c\)
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
+ bar\(a=a, b=b, c=c\)
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
+ foo\(1, 2, 3\)
$''')
@unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands")
@@ -763,10 +769,18 @@ def test_up_then_down(self):
cmds_after_breakpoint=['py-up', 'py-up', 'py-down'])
self.assertMultilineMatches(bt,
r'''^.*
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 10, in baz \(args=\(1, 2, 3\)\)
+ id\(42\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
baz\(a, b, c\)
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
+ bar\(a=a, b=b, c=c\)
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
+ foo\(1, 2, 3\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 10, in baz \(args=\(1, 2, 3\)\)
id\(42\)
+#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
+ baz\(a, b, c\)
$''')
class PyBtTests(DebuggerTests):
@@ -785,7 +799,7 @@ def test_bt(self):
File ".*gdb_sample.py", line 7, in bar
baz\(a, b, c\)
File ".*gdb_sample.py", line 4, in foo
- bar\(a, b, c\)
+ bar\(a=a, b=b, c=c\)
File ".*gdb_sample.py", line 12, in <module>
foo\(1, 2, 3\)
''')
@@ -801,7 +815,7 @@ def test_bt_full(self):
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
baz\(a, b, c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
- bar\(a, b, c\)
+ bar\(a=a, b=b, c=c\)
#[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in <module> \(\)
foo\(1, 2, 3\)
''')
@@ -1008,7 +1022,13 @@ def test_locals_after_up(self):
bt = self.get_stack_trace(script=self.get_sample_script(),
cmds_after_breakpoint=['py-up', 'py-up', 'py-locals'])
self.assertMultilineMatches(bt,
- r".*\na = 1\nb = 2\nc = 3\n.*")
+ r'''^.*
+Locals for foo
+a = 1
+b = 2
+c = 3
+Locals for <module>
+.*$''')
def setUpModule():
diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c
index e64b93ba63e41..018e738af06e3 100644
--- a/Objects/tupleobject.c
+++ b/Objects/tupleobject.c
@@ -484,6 +484,26 @@ _PyTuple_FromArray(PyObject *const *src, Py_ssize_t n)
return (PyObject *)tuple;
}
+PyObject *
+_PyTuple_FromArraySteal(PyObject *const *src, Py_ssize_t n)
+{
+ if (n == 0) {
+ return tuple_get_empty();
+ }
+
+ PyTupleObject *tuple = tuple_alloc(n);
+ if (tuple == NULL) {
+ return NULL;
+ }
+ PyObject **dst = tuple->ob_item;
+ for (Py_ssize_t i = 0; i < n; i++) {
+ PyObject *item = src[i];
+ dst[i] = item;
+ }
+ _PyObject_GC_TRACK(tuple);
+ return (PyObject *)tuple;
+}
+
static PyObject *
tupleslice(PyTupleObject *a, Py_ssize_t ilow,
Py_ssize_t ihigh)
diff --git a/Python/ceval.c b/Python/ceval.c
index e39ec67614bf5..6620c00d0f981 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -98,6 +98,12 @@ static int check_args_iterable(PyThreadState *, PyObject *func, PyObject *vararg
static void format_kwargs_error(PyThreadState *, PyObject *func, PyObject *kwargs);
static void format_awaitable_error(PyThreadState *, PyTypeObject *, int, int);
static int get_exception_handler(PyCodeObject *, int, int*, int*, int*);
+static InterpreterFrame *
+_PyEvalFramePushAndInit(PyThreadState *tstate, PyFrameConstructor *con,
+ PyObject *locals, PyObject* const* args,
+ size_t argcount, PyObject *kwnames, int steal_args);
+static int
+_PyEvalFrameClearAndPop(PyThreadState *tstate, InterpreterFrame * frame);
#define NAME_ERROR_MSG \
"name '%.200s' is not defined"
@@ -1516,6 +1522,12 @@ trace_function_entry(PyThreadState *tstate, InterpreterFrame *frame)
return 0;
}
+static PyObject *
+make_coro(PyThreadState *tstate, PyFrameConstructor *con,
+ PyObject *locals,
+ PyObject* const* args, size_t argcount,
+ PyObject *kwnames);
+
static int
skip_backwards_over_extended_args(PyCodeObject *code, int offset) {
_Py_CODEUNIT *instrs = (_Py_CODEUNIT *)PyBytes_AS_STRING(code->co_code);
@@ -1543,10 +1555,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
PyObject *retval = NULL; /* Return value */
_Py_atomic_int * const eval_breaker = &tstate->interp->ceval.eval_breaker;
- if (_Py_EnterRecursiveCall(tstate, "")) {
- return NULL;
- }
-
CFrame cframe;
/* WARNING: Because the CFrame lives on the C stack,
@@ -1558,9 +1566,18 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
cframe.previous = prev_cframe;
tstate->cframe = &cframe;
+ assert(frame->depth == 0);
/* push frame */
tstate->frame = frame;
+start_frame:
+ if (_Py_EnterRecursiveCall(tstate, "")) {
+ tstate->recursion_depth++;
+ goto exit_eval_frame;
+ }
+
+ assert(frame == tstate->frame);
+
if (cframe.use_tracing) {
if (trace_function_entry(tstate, frame)) {
goto exit_eval_frame;
@@ -1582,7 +1599,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
}
}
-
+resume_frame:
+ co = frame->f_code;
PyObject *names = co->co_names;
PyObject *consts = co->co_consts;
_Py_CODEUNIT *first_instr = co->co_firstinstr;
@@ -1594,12 +1612,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
multiple values.
When the PREDICT() macros are enabled, some opcode pairs follow in
- direct succession without updating frame->f_lasti. A successful
- prediction effectively links the two codes together as if they
- were a single new opcode; accordingly,frame->f_lasti will point to
- the first code in the pair (for instance, GET_ITER followed by
- FOR_ITER is effectively a single opcode and frame->f_lasti will point
- to the beginning of the combined pair.)
+ direct succession. A successful prediction effectively links the two
+ codes together as if they were a single new opcode, but the value
+ of frame->f_lasti is correctly updated so potential inlined calls
+ or lookups of frame->f_lasti are aways correct when the macros are used.
*/
assert(frame->f_lasti >= -1);
_Py_CODEUNIT *next_instr = first_instr + frame->f_lasti + 1;
@@ -1625,6 +1641,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
#endif
if (throwflag) { /* support for generator.throw() */
+ throwflag = 0;
goto error;
}
@@ -4591,10 +4608,44 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
TARGET(CALL_FUNCTION) {
PREDICTED(CALL_FUNCTION);
- PyObject **sp, *res;
- sp = stack_pointer;
- res = call_function(tstate, &sp, oparg, NULL, cframe.use_tracing);
- stack_pointer = sp;
+ PyObject *res;
+
+ // Check if the call can be inlined or not
+ PyObject *function = PEEK(oparg + 1);
+ if (Py_TYPE(function) == &PyFunction_Type) {
+ PyCodeObject *code = (PyCodeObject*)PyFunction_GET_CODE(function);
+ PyObject *locals = code->co_flags & CO_OPTIMIZED ? NULL : PyFunction_GET_GLOBALS(function);
+ if ((code->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) == 0) {
+ InterpreterFrame *new_frame = _PyEvalFramePushAndInit(
+ tstate, PyFunction_AS_FRAME_CONSTRUCTOR(function), locals, stack_pointer-oparg, oparg, NULL, 1);
+ if (new_frame == NULL) {
+ // When we exit here, we own all variables in the stack (the frame creation has not stolen
+ // any variable) so we need to clean the whole stack (done in the "error" label).
+ goto error;
+ }
+ STACK_SHRINK(oparg + 1);
+ assert(tstate->interp->eval_frame != NULL);
+ // The frame has stolen all the arguments from the stack, so there is no need to clean them up.```
+ Py_DECREF(function);
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ new_frame->depth = frame->depth + 1;
+ tstate->frame = frame = new_frame;
+ goto start_frame;
+ }
+ else {
+ /* Callable is a generator or coroutine function: create coroutine or generator. */
+ res = make_coro(tstate, PyFunction_AS_FRAME_CONSTRUCTOR(function), locals, stack_pointer-oparg, oparg, NULL);
+ STACK_SHRINK(oparg + 1);
+ for (int i = 0; i < oparg + 1; i++) {
+ Py_DECREF(stack_pointer[i]);
+ }
+ }
+ }
+ else {
+ PyObject **sp = stack_pointer;
+ res = call_function(tstate, &sp, oparg, NULL, cframe.use_tracing);
+ stack_pointer = sp;
+ }
PUSH(res);
if (res == NULL) {
goto error;
@@ -5018,14 +5069,28 @@ MISS_WITH_OPARG_COUNTER(BINARY_ADD)
/* pop frame */
exit_eval_frame:
- /* Restore previous cframe */
- tstate->cframe = cframe.previous;
- tstate->cframe->use_tracing = cframe.use_tracing;
-
if (PyDTrace_FUNCTION_RETURN_ENABLED())
dtrace_function_return(frame);
_Py_LeaveRecursiveCall(tstate);
+
+ if (frame->depth) {
+ _PyFrame_StackPush(frame->previous, retval);
+ if (_PyEvalFrameClearAndPop(tstate, frame)) {
+ retval = NULL;
+ }
+ frame = tstate->frame;
+ if (retval == NULL) {
+ assert(_PyErr_Occurred(tstate));
+ throwflag = 1;
+ }
+ retval = NULL;
+ goto resume_frame;
+ }
tstate->frame = frame->previous;
+
+ /* Restore previous cframe */
+ tstate->cframe = cframe.previous;
+ tstate->cframe->use_tracing = cframe.use_tracing;
return _Py_CheckFunctionResult(tstate, NULL, retval, __func__);
}
@@ -5336,7 +5401,7 @@ get_exception_handler(PyCodeObject *code, int index, int *level, int *handler, i
static int
initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
PyObject **localsplus, PyObject *const *args,
- Py_ssize_t argcount, PyObject *kwnames)
+ Py_ssize_t argcount, PyObject *kwnames, int steal_args)
{
PyCodeObject *co = (PyCodeObject*)con->fc_code;
const Py_ssize_t total_args = co->co_argcount + co->co_kwonlyargcount;
@@ -5346,8 +5411,9 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
Py_ssize_t i;
if (co->co_flags & CO_VARKEYWORDS) {
kwdict = PyDict_New();
- if (kwdict == NULL)
+ if (kwdict == NULL) {
goto fail;
+ }
i = total_args;
if (co->co_flags & CO_VARARGS) {
i++;
@@ -5369,14 +5435,21 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
}
for (j = 0; j < n; j++) {
PyObject *x = args[j];
- Py_INCREF(x);
+ if (!steal_args) {
+ Py_INCREF(x);
+ }
assert(localsplus[j] == NULL);
localsplus[j] = x;
}
/* Pack other positional arguments into the *args argument */
if (co->co_flags & CO_VARARGS) {
- PyObject *u = _PyTuple_FromArray(args + n, argcount - n);
+ PyObject *u = NULL;
+ if (steal_args) {
+ u = _PyTuple_FromArraySteal(args + n, argcount - n);
+ } else {
+ u = _PyTuple_FromArray(args + n, argcount - n);
+ }
if (u == NULL) {
goto fail;
}
@@ -5442,6 +5515,9 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
if (PyDict_SetItem(kwdict, keyword, value) == -1) {
goto fail;
}
+ if (steal_args) {
+ Py_DECREF(value);
+ }
continue;
kw_found:
@@ -5451,7 +5527,9 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
con->fc_qualname, keyword);
goto fail;
}
- Py_INCREF(value);
+ if (!steal_args) {
+ Py_INCREF(value);
+ }
localsplus[j] = value;
}
}
@@ -5555,7 +5633,7 @@ make_coro_frame(PyThreadState *tstate,
}
_PyFrame_InitializeSpecials(frame, con, locals, code->co_nlocalsplus);
assert(frame->frame_obj == NULL);
- if (initialize_locals(tstate, con, frame->localsplus, args, argcount, kwnames)) {
+ if (initialize_locals(tstate, con, frame->localsplus, args, argcount, kwnames, 0)) {
_PyFrame_Clear(frame, 1);
return NULL;
}
@@ -5581,17 +5659,30 @@ make_coro(PyThreadState *tstate, PyFrameConstructor *con,
return gen;
}
+// If *steal_args* is set, the function will steal the references to all the arguments.
+// In case of error, the function returns null and if *steal_args* is set, the caller
+// will still own all the arguments.
static InterpreterFrame *
_PyEvalFramePushAndInit(PyThreadState *tstate, PyFrameConstructor *con,
PyObject *locals, PyObject* const* args,
- size_t argcount, PyObject *kwnames)
+ size_t argcount, PyObject *kwnames, int steal_args)
{
InterpreterFrame * frame = _PyThreadState_PushFrame(tstate, con, locals);
if (frame == NULL) {
return NULL;
}
PyObject **localsarray = _PyFrame_GetLocalsArray(frame);
- if (initialize_locals(tstate, con, localsarray, args, argcount, kwnames)) {
+ if (initialize_locals(tstate, con, localsarray, args, argcount, kwnames, steal_args)) {
+ if (steal_args) {
+ // If we failed to initialize locals, make sure the caller still own all the
+ // arguments. Notice that we only need to increase the reference count of the
+ // *valid* arguments (i.e. the ones that fit into the frame).
+ PyCodeObject *co = (PyCodeObject*)con->fc_code;
+ const Py_ssize_t total_args = co->co_argcount + co->co_kwonlyargcount;
+ for (Py_ssize_t i = 0; i < Py_MIN(argcount, total_args); i++) {
+ Py_XINCREF(frame->localsplus[i]);
+ }
+ }
_PyFrame_Clear(frame, 0);
return NULL;
}
@@ -5606,9 +5697,9 @@ _PyEvalFrameClearAndPop(PyThreadState *tstate, InterpreterFrame * frame)
++tstate->recursion_depth;
assert(frame->frame_obj == NULL || frame->frame_obj->f_own_locals_memory == 0);
if (_PyFrame_Clear(frame, 0)) {
+ --tstate->recursion_depth;
return -1;
}
- assert(frame->frame_obj == NULL);
--tstate->recursion_depth;
tstate->frame = frame->previous;
_PyThreadState_PopFrame(tstate, frame);
@@ -5628,7 +5719,7 @@ _PyEval_Vector(PyThreadState *tstate, PyFrameConstructor *con,
return make_coro(tstate, con, locals, args, argcount, kwnames);
}
InterpreterFrame *frame = _PyEvalFramePushAndInit(
- tstate, con, locals, args, argcount, kwnames);
+ tstate, con, locals, args, argcount, kwnames, 0);
if (frame == NULL) {
return NULL;
}
diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py
index 62eb1976b715f..a118d326dc00e 100755
--- a/Tools/gdb/libpython.py
+++ b/Tools/gdb/libpython.py
@@ -45,6 +45,7 @@
# compatible (2.6+ and 3.0+). See #19308.
from __future__ import print_function
+
import gdb
import os
import locale
@@ -991,6 +992,11 @@ def _f_nlocalsplus(self):
def _f_lasti(self):
return self._f_special("f_lasti", int_from_int)
+ def depth(self):
+ return self._f_special("depth", int_from_int)
+
+ def previous(self):
+ return self._f_special("previous", PyFramePtr)
def iter_globals(self):
'''
@@ -1797,16 +1803,20 @@ def get_selected_bytecode_frame(cls):
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))
- 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())
+ interp_frame = self.get_pyop()
+ while True:
+ if interp_frame:
+ line = interp_frame.get_truncated_repr(MAX_OUTPUT_LEN)
+ write_unicode(sys.stdout, '#%i %s\n' % (self.get_index(), line))
+ if not interp_frame.is_optimized_out():
+ line = interp_frame.current_line()
+ if line is not None:
+ sys.stdout.write(' %s\n' % line.strip())
+ if interp_frame.depth() == 0:
+ break
+ else:
+ sys.stdout.write('#%i (unable to read python frame information)\n' % self.get_index())
+ interp_frame = interp_frame.previous()
else:
info = self.is_other_python_frame()
if info:
@@ -1816,15 +1826,19 @@ def print_summary(self):
def print_traceback(self):
if self.is_evalframe():
- pyop = self.get_pyop()
- if pyop:
- pyop.print_traceback()
- 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(' (unable to read python frame information)\n')
+ interp_frame = self.get_pyop()
+ while True:
+ if interp_frame:
+ interp_frame.print_traceback()
+ if not interp_frame.is_optimized_out():
+ line = interp_frame.current_line()
+ if line is not None:
+ sys.stdout.write(' %s\n' % line.strip())
+ if interp_frame.depth() == 0:
+ break
+ else:
+ sys.stdout.write(' (unable to read python frame information)\n')
+ interp_frame = interp_frame.previous()
else:
info = self.is_other_python_frame()
if info:
@@ -1914,11 +1928,15 @@ def invoke(self, args, from_tty):
def move_in_stack(move_up):
'''Move up or down the stack (for the py-up/py-down command)'''
+ # Important:
+ # The amount of frames that are printed out depends on how many frames are inlined
+ # in the same evaluation loop. As this command links directly the C stack with the
+ # Python stack, the results are sensitive to the number of inlined frames and this
+ # is likely to change between versions and optimizations.
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()
@@ -1940,9 +1958,10 @@ def move_in_stack(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)'
+ 'Select and print all python stack frame in the same eval loop starting from the one that called this one (if any)'
def __init__(self):
gdb.Command.__init__ (self,
"py-up",
@@ -1954,7 +1973,7 @@ 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)'
+ 'Select and print all python stack frame in the same eval loop starting from the one called this one (if any)'
def __init__(self):
gdb.Command.__init__ (self,
"py-down",
@@ -2067,13 +2086,20 @@ def invoke(self, args, from_tty):
return
pyop_frame = frame.get_pyop()
- if not pyop_frame:
- print(UNABLE_READ_INFO_PYTHON_FRAME)
- return
+ while True:
+ if not pyop_frame:
+ print(UNABLE_READ_INFO_PYTHON_FRAME)
+
+ sys.stdout.write('Locals for %s\n' % (pyop_frame.co_name.proxyval(set())))
+
+ for pyop_name, pyop_value in pyop_frame.iter_locals():
+ print('%s = %s'
+ % (pyop_name.proxyval(set()),
+ pyop_value.get_truncated_repr(MAX_OUTPUT_LEN)))
+
+ if pyop_frame.depth() == 0:
+ break
- for pyop_name, pyop_value in pyop_frame.iter_locals():
- print('%s = %s'
- % (pyop_name.proxyval(set()),
- pyop_value.get_truncated_repr(MAX_OUTPUT_LEN)))
+ pyop_frame = pyop_frame.previous()
PyLocals()
More information about the Python-checkins
mailing list