mirror of
https://github.com/python/cpython.git
synced 2025-12-07 13:50:06 +00:00
gh-141070: Add PyUnstable_Object_Dump() function (#141072)
* Promote _PyObject_Dump() as a public function. * Keep _PyObject_Dump() alias to PyUnstable_Object_Dump() for backward compatibility. * Replace _PyObject_Dump() with PyUnstable_Object_Dump(). Co-authored-by: Peter Bierma <zintensitydev@gmail.com> Co-authored-by: Kumar Aditya <kumaraditya@python.org> Co-authored-by: Petr Viktorin <encukou@gmail.com>
This commit is contained in:
parent
4695ec109d
commit
600f3feb23
11 changed files with 135 additions and 18 deletions
|
|
@ -85,6 +85,35 @@ Object Protocol
|
|||
instead of the :func:`repr`.
|
||||
|
||||
|
||||
.. c:function:: void PyUnstable_Object_Dump(PyObject *op)
|
||||
|
||||
Dump an object *op* to ``stderr``. This should only be used for debugging.
|
||||
|
||||
The output is intended to try dumping objects even after memory corruption:
|
||||
|
||||
* Information is written starting with fields that are the least likely to
|
||||
crash when accessed.
|
||||
* This function can be called without an :term:`attached thread state`, but
|
||||
it's not recommended to do so: it can cause deadlocks.
|
||||
* An object that does not belong to the current interpreter may be dumped,
|
||||
but this may also cause crashes or unintended behavior.
|
||||
* Implement a heuristic to detect if the object memory has been freed. Don't
|
||||
display the object contents in this case, only its memory address.
|
||||
* The output format may change at any time.
|
||||
|
||||
Example of output:
|
||||
|
||||
.. code-block:: output
|
||||
|
||||
object address : 0x7f80124702c0
|
||||
object refcount : 2
|
||||
object type : 0x9902e0
|
||||
object type name: str
|
||||
object repr : 'abcdef'
|
||||
|
||||
.. versionadded:: next
|
||||
|
||||
|
||||
.. c:function:: int PyObject_HasAttrWithError(PyObject *o, PyObject *attr_name)
|
||||
|
||||
Returns ``1`` if *o* has the attribute *attr_name*, and ``0`` otherwise.
|
||||
|
|
|
|||
|
|
@ -1084,19 +1084,23 @@ New features
|
|||
|
||||
(Contributed by Victor Stinner in :gh:`129813`.)
|
||||
|
||||
* Add a new :c:func:`PyImport_CreateModuleFromInitfunc` C-API for creating
|
||||
a module from a *spec* and *initfunc*.
|
||||
(Contributed by Itamar Oren in :gh:`116146`.)
|
||||
|
||||
* Add :c:func:`PyTuple_FromArray` to create a :class:`tuple` from an array.
|
||||
(Contributed by Victor Stinner in :gh:`111489`.)
|
||||
|
||||
* Add :c:func:`PyUnstable_Object_Dump` to dump an object to ``stderr``.
|
||||
It should only be used for debugging.
|
||||
(Contributed by Victor Stinner in :gh:`141070`.)
|
||||
|
||||
* Add :c:func:`PyUnstable_ThreadState_SetStackProtection` and
|
||||
:c:func:`PyUnstable_ThreadState_ResetStackProtection` functions to set
|
||||
the stack protection base address and stack protection size of a Python
|
||||
thread state.
|
||||
(Contributed by Victor Stinner in :gh:`139653`.)
|
||||
|
||||
* Add a new :c:func:`PyImport_CreateModuleFromInitfunc` C-API for creating
|
||||
a module from a *spec* and *initfunc*.
|
||||
(Contributed by Itamar Oren in :gh:`116146`.)
|
||||
|
||||
|
||||
Changed C APIs
|
||||
--------------
|
||||
|
|
|
|||
|
|
@ -295,7 +295,10 @@ PyAPI_FUNC(PyObject *) PyType_GetDict(PyTypeObject *);
|
|||
|
||||
PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int);
|
||||
PyAPI_FUNC(void) _Py_BreakPoint(void);
|
||||
PyAPI_FUNC(void) _PyObject_Dump(PyObject *);
|
||||
PyAPI_FUNC(void) PyUnstable_Object_Dump(PyObject *);
|
||||
|
||||
// Alias for backward compatibility
|
||||
#define _PyObject_Dump PyUnstable_Object_Dump
|
||||
|
||||
PyAPI_FUNC(PyObject*) _PyObject_GetAttrId(PyObject *, _Py_Identifier *);
|
||||
|
||||
|
|
@ -387,10 +390,11 @@ PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *);
|
|||
process with a message on stderr if the given condition fails to hold,
|
||||
but compile away to nothing if NDEBUG is defined.
|
||||
|
||||
However, before aborting, Python will also try to call _PyObject_Dump() on
|
||||
the given object. This may be of use when investigating bugs in which a
|
||||
particular object is corrupt (e.g. buggy a tp_visit method in an extension
|
||||
module breaking the garbage collector), to help locate the broken objects.
|
||||
However, before aborting, Python will also try to call
|
||||
PyUnstable_Object_Dump() on the given object. This may be of use when
|
||||
investigating bugs in which a particular object is corrupt (e.g. buggy a
|
||||
tp_visit method in an extension module breaking the garbage collector), to
|
||||
help locate the broken objects.
|
||||
|
||||
The WITH_MSG variant allows you to supply an additional message that Python
|
||||
will attempt to print to stderr, after the object dump. */
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ static inline void
|
|||
_PyStaticObject_CheckRefcnt(PyObject *obj) {
|
||||
if (!_Py_IsImmortal(obj)) {
|
||||
fprintf(stderr, "Immortal Object has less refcnt than expected.\n");
|
||||
_PyObject_Dump(obj);
|
||||
PyUnstable_Object_Dump(obj);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import enum
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
import unittest
|
||||
|
|
@ -13,6 +14,9 @@
|
|||
_testcapi = import_helper.import_module('_testcapi')
|
||||
_testinternalcapi = import_helper.import_module('_testinternalcapi')
|
||||
|
||||
NULL = None
|
||||
STDERR_FD = 2
|
||||
|
||||
|
||||
class Constant(enum.IntEnum):
|
||||
Py_CONSTANT_NONE = 0
|
||||
|
|
@ -247,5 +251,53 @@ def func(x):
|
|||
|
||||
func(object())
|
||||
|
||||
def pyobject_dump(self, obj, release_gil=False):
|
||||
pyobject_dump = _testcapi.pyobject_dump
|
||||
|
||||
try:
|
||||
old_stderr = os.dup(STDERR_FD)
|
||||
except OSError as exc:
|
||||
# os.dup(STDERR_FD) is not supported on WASI
|
||||
self.skipTest(f"os.dup() failed with {exc!r}")
|
||||
|
||||
filename = os_helper.TESTFN
|
||||
try:
|
||||
try:
|
||||
with open(filename, "wb") as fp:
|
||||
fd = fp.fileno()
|
||||
os.dup2(fd, STDERR_FD)
|
||||
pyobject_dump(obj, release_gil)
|
||||
finally:
|
||||
os.dup2(old_stderr, STDERR_FD)
|
||||
os.close(old_stderr)
|
||||
|
||||
with open(filename) as fp:
|
||||
return fp.read().rstrip()
|
||||
finally:
|
||||
os_helper.unlink(filename)
|
||||
|
||||
def test_pyobject_dump(self):
|
||||
# test string object
|
||||
str_obj = 'test string'
|
||||
output = self.pyobject_dump(str_obj)
|
||||
hex_regex = r'(0x)?[0-9a-fA-F]+'
|
||||
regex = (
|
||||
fr"object address : {hex_regex}\n"
|
||||
r"object refcount : [0-9]+\n"
|
||||
fr"object type : {hex_regex}\n"
|
||||
r"object type name: str\n"
|
||||
r"object repr : 'test string'"
|
||||
)
|
||||
self.assertRegex(output, regex)
|
||||
|
||||
# release the GIL
|
||||
output = self.pyobject_dump(str_obj, release_gil=True)
|
||||
self.assertRegex(output, regex)
|
||||
|
||||
# test NULL object
|
||||
output = self.pyobject_dump(NULL)
|
||||
self.assertRegex(output, r'<object at .* is freed>')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
Add :c:func:`PyUnstable_Object_Dump` to dump an object to ``stderr``. It should
|
||||
only be used for debugging. Patch by Victor Stinner.
|
||||
|
|
@ -485,6 +485,30 @@ is_uniquely_referenced(PyObject *self, PyObject *op)
|
|||
}
|
||||
|
||||
|
||||
static PyObject *
|
||||
pyobject_dump(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *op;
|
||||
int release_gil = 0;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "O|i", &op, &release_gil)) {
|
||||
return NULL;
|
||||
}
|
||||
NULLABLE(op);
|
||||
|
||||
if (release_gil) {
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
PyUnstable_Object_Dump(op);
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
}
|
||||
else {
|
||||
PyUnstable_Object_Dump(op);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef test_methods[] = {
|
||||
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
|
||||
{"pyobject_print_null", pyobject_print_null, METH_VARARGS},
|
||||
|
|
@ -511,6 +535,7 @@ static PyMethodDef test_methods[] = {
|
|||
{"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
|
||||
{"clear_managed_dict", clear_managed_dict, METH_O, NULL},
|
||||
{"is_uniquely_referenced", is_uniquely_referenced, METH_O},
|
||||
{"pyobject_dump", pyobject_dump, METH_VARARGS},
|
||||
{NULL},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -713,7 +713,7 @@ _PyObject_IsFreed(PyObject *op)
|
|||
|
||||
/* For debugging convenience. See Misc/gdbinit for some useful gdb hooks */
|
||||
void
|
||||
_PyObject_Dump(PyObject* op)
|
||||
PyUnstable_Object_Dump(PyObject* op)
|
||||
{
|
||||
if (_PyObject_IsFreed(op)) {
|
||||
/* It seems like the object memory has been freed:
|
||||
|
|
@ -3150,7 +3150,7 @@ _PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,
|
|||
|
||||
/* This might succeed or fail, but we're about to abort, so at least
|
||||
try to provide any extra info we can: */
|
||||
_PyObject_Dump(obj);
|
||||
PyUnstable_Object_Dump(obj);
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
fflush(stderr);
|
||||
|
|
|
|||
|
|
@ -547,7 +547,8 @@ unicode_check_encoding_errors(const char *encoding, const char *errors)
|
|||
}
|
||||
|
||||
/* Disable checks during Python finalization. For example, it allows to
|
||||
call _PyObject_Dump() during finalization for debugging purpose. */
|
||||
* call PyUnstable_Object_Dump() during finalization for debugging purpose.
|
||||
*/
|
||||
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2237,7 +2237,7 @@ _PyGC_Fini(PyInterpreterState *interp)
|
|||
void
|
||||
_PyGC_Dump(PyGC_Head *g)
|
||||
{
|
||||
_PyObject_Dump(FROM_GC(g));
|
||||
PyUnstable_Object_Dump(FROM_GC(g));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1181,7 +1181,7 @@ _PyErr_Display(PyObject *file, PyObject *unused, PyObject *value, PyObject *tb)
|
|||
}
|
||||
if (print_exception_recursive(&ctx, value) < 0) {
|
||||
PyErr_Clear();
|
||||
_PyObject_Dump(value);
|
||||
PyUnstable_Object_Dump(value);
|
||||
fprintf(stderr, "lost sys.stderr\n");
|
||||
}
|
||||
Py_XDECREF(ctx.seen);
|
||||
|
|
@ -1199,14 +1199,14 @@ PyErr_Display(PyObject *unused, PyObject *value, PyObject *tb)
|
|||
PyObject *file;
|
||||
if (PySys_GetOptionalAttr(&_Py_ID(stderr), &file) < 0) {
|
||||
PyObject *exc = PyErr_GetRaisedException();
|
||||
_PyObject_Dump(value);
|
||||
PyUnstable_Object_Dump(value);
|
||||
fprintf(stderr, "lost sys.stderr\n");
|
||||
_PyObject_Dump(exc);
|
||||
PyUnstable_Object_Dump(exc);
|
||||
Py_DECREF(exc);
|
||||
return;
|
||||
}
|
||||
if (file == NULL) {
|
||||
_PyObject_Dump(value);
|
||||
PyUnstable_Object_Dump(value);
|
||||
fprintf(stderr, "lost sys.stderr\n");
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue