Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pdb throws AttributeError at end of debugging session #57253

Closed
akl mannequin opened this issue Sep 25, 2011 · 12 comments
Closed

pdb throws AttributeError at end of debugging session #57253

akl mannequin opened this issue Sep 25, 2011 · 12 comments
Labels
stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@akl
Copy link
Mannequin

akl mannequin commented Sep 25, 2011

BPO 13044
Nosy @amauryfa, @nedbat, @asvetlov, @meadori, @xdegaye, @serhiy-storchaka, @applio
Files
  • debug.py: Trivial script to demonstrate issue
  • bdb_leak.diff
  • lazy_import.diff
  • gdb_backtrace.txt
  • global_destructors.diff
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2011-09-25.16:36:22.166>
    labels = ['type-bug', 'library']
    title = 'pdb throws AttributeError at end of debugging session'
    updated_at = <Date 2018-05-10.20:36:35.281>
    user = 'https://bugs.python.org/akl'

    bugs.python.org fields:

    activity = <Date 2018-05-10.20:36:35.281>
    actor = 'xdegaye'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Library (Lib)']
    creation = <Date 2011-09-25.16:36:22.166>
    creator = 'akl'
    dependencies = []
    files = ['23242', '47577', '47578', '47579', '47581']
    hgrepos = []
    issue_num = 13044
    keywords = ['patch']
    message_count = 11.0
    messages = ['144525', '144534', '176273', '176987', '176993', '242344', '242345', '315894', '316297', '316373', '316377']
    nosy_count = 9.0
    nosy_names = ['amaury.forgeotdarc', 'nedbat', 'asvetlov', 'meador.inge', 'xdegaye', 'akl', 'serhiy.storchaka', 'davin', 'cfbearden']
    pr_nums = []
    priority = 'normal'
    resolution = None
    stage = None
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue13044'
    versions = ['Python 2.7']

    @akl
    Copy link
    Mannequin Author

    akl mannequin commented Sep 25, 2011

    Using Python 2.7.1 on OpenBSD-current and 2.7.2 on Arch Linux, pdb throws a (harmless, it appears) exception at the end of a debugging session. This does not happen for me using Python 2.5 or 2.6 or 3.2.

    Console Session (on linux):

    $ python2 --version
    Python 2.7.2
    
    $ python2 debug.py 
    > /tmp/debug.py(4)<module>()
    -> a = 1
    (Pdb) n
    > /tmp/debug.py(5)<module>()
    -> b = 2
    (Pdb) n
    > /tmp/debug.py(6)<module>()
    -> sum = a + b
    (Pdb) n
    --Return--
    > /tmp/debug.py(6)<module>()->None
    -> sum = a + b
    (Pdb) n
    Exception AttributeError: "'NoneType' object has no attribute 'path'" in <function _remove at 0xb73b6df4> ignore

    @akl akl mannequin added stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error labels Sep 25, 2011
    @amauryfa
    Copy link
    Member

    By modifying a bit the Python intepreter, I got this traceback:

    Traceback (most recent call last):
      File "/home/amauryfa/python/cpython2.7/Lib/_weakrefset.py", line 38, in _remove
        def _remove(item, selfref=ref(self)):
      File "/home/amauryfa/python/cpython2.7/Lib/bdb.py", line 50, in trace_dispatch
        return self.dispatch_call(frame, arg)
      File "/home/amauryfa/python/cpython2.7/Lib/bdb.py", line 76, in dispatch_call
        if not (self.stop_here(frame) or self.break_anywhere(frame)):
      File "/home/amauryfa/python/cpython2.7/Lib/bdb.py", line 147, in break_anywhere
        return self.canonic(frame.f_code.co_filename) in self.breaks
      File "/home/amauryfa/python/cpython2.7/Lib/bdb.py", line 33, in canonic
        canonic = os.path.abspath(filename)
    AttributeError: 'NoneType' object has no attribute 'path'

    Here is the gdb stack when the error is printed:

    #0 PyErr_Print () at Python/pythonrun.c:1040
    #1 0x0000000000480c1d in handle_callback (object=<value optimized out>) at Objects/weakrefobject.c:884
    #2 PyObject_ClearWeakRefs (object=<value optimized out>) at Objects/weakrefobject.c:966
    #3 0x0000000000423a37 in class_dealloc (op=0x7ffff7f68890) at Objects/classobject.c:193
    #4 0x000000000046a7ed in tupledealloc (op=0x7ffff7f89410) at Objects/tupleobject.c:220
    #5 0x0000000000423afb in class_dealloc (op=0x7ffff7f7ed50) at Objects/classobject.c:194
    #6 0x00000000004270cb in instance_dealloc (inst=0x7ffff7f8a518) at Objects/classobject.c:670
    #7 0x000000000044de47 in insertdict (mp=0x7f7800, key='environ', hash=-5347984860299468300, value=None)
    at Objects/dictobject.c:530
    #8 0x0000000000450287 in PyDict_SetItem (op=
    {'WTERMSIG': None, 'lseek': None, 'EX_IOERR': None, 'EX_NOHOST': None, 'seteuid': None, 'pathsep': None, 'execle': None, 'major': None, '_Environ': None, 'fstatvfs': None, 'uname': None, 'kill': None, 'urandom': None, 'execlp': None, 'getegid': None, 'getresgid': None, 'EX_OSFILE': None, 'umask': None, 'linesep': None, 'fchmod': None, 'lchown': None, 'setgid': None, 'tmpnam': None, 'devnull': None, 'EX_NOINPUT': None, 'makedev': None, 'fstat': None, 'getlogin': None, 'O_CREAT': None, 'dup2': None, 'read': None, '__file__': None, 'getppid': None, 'fchown': None, 'getloadavg': None, 'WIFSTOPPED': None, 'getpgrp': None, '_spawnvef': None, 'TMP_MAX': None, 'utime': None, 'execl': None, 'F_OK': None, '_make_stat_result': None, 'name': None, 'fsync': None, 'tcsetpgrp': None, 'statvfs': None, 'setreuid': None, 'remove': None, 'setegid': None, 'P_NOWAITO': None, '_copy_reg': None, 'execv': None, 'spawnv': None, 'spawnvpe': None, 'EX_OSERR': None, 'ttyname': None, 'pardir': None, 'tempnam': None, 'tmpfile': None, 'sep...(truncated), key='environ', value=None) at Objects/dictobject.c:775
    #9 0x000000000045268e in _PyModule_Clear (m=<value optimized out>) at Objects/moduleobject.c:138
    #10 0x00000000004bfa17 in PyImport_Cleanup () at Python/import.c:498
    #11 0x00000000004cd9ef in Py_Finalize () at Python/pythonrun.c:447

    So, when the interpreter shuts down, the debugger is still active...
    Python 3 does not seem to be affected by this issue. And indeed if I make UserDict a new-style class, I don't reproduce the error on 2.7 either.

    A possible fix is to reset the trace function to None in Py_Finalize.

    @xdegaye
    Copy link
    Mannequin

    xdegaye mannequin commented Nov 24, 2012

    The run, runeval and runcall methods run the debugging session in a
    try/finally clause and set the global trace function to None in the
    finally clause. But set_trace does not run in a try/finally, hence the
    problem. A possible fix is to ensure that the bottom frame has a local
    trace function and to set the global trace function to None when
    returning from that frame. See how this is fixed at
    http://code.google.com/p/pdb-clone/source/detail?r=ee22a278c743ea9449a3e7618acc7c33cb11bb26&name=2.7

    @asvetlov
    Copy link
    Contributor

    asvetlov commented Dec 5, 2012

    Do we need to fix it at all?
    On finalizing pdb can stop working at some time, but debugging on finalization stage can be still useful in certain cases.

    Xavier, your proposition noticeably changes current behavior as I understand it.
    For now set_trace() works as breakpoint set up by program, you can go out of debugged function and it's very convenient from my perspective.

    @xdegaye
    Copy link
    Mannequin

    xdegaye mannequin commented Dec 5, 2012

    On finalizing pdb can stop working at some time, but debugging on
    finalization stage can be still useful in certain cases.

    Agreed that debugging on finalization stage is useful. Debugging on
    finalization stage does not seem to work though:

    ===============================

    class C:
        def __del__(self):
            print('deleted')
    
    c = C()
    import pdb; pdb.set_trace()
    x = 1

    ===============================

    $ python3 bar.py
    > /tmp/bar.py(7)<module>()
    -> x = 1
    (Pdb) step
    --Return--
    > /tmp/bar.py(7)<module>()->None
    -> x = 1
    (Pdb) step
    deleted
    $

    ===============================

    Maybe this is another issue.

    Xavier, your proposition noticeably changes current behavior as I understand
    it. For now set_trace() works as breakpoint set up by program, you can go
    out of debugged function and it's very convenient from my perspective.

    This proposition only sets the global trace function to None on
    returning from the bottom frame, i.e. the oldest frame in the stack. So you can
    still use set_trace() as a hard-coded breakpoint and continue the debugging
    session out of the debugged function.

    @cfbearden
    Copy link
    Mannequin

    cfbearden mannequin commented May 1, 2015

    I can reproduce this bug on Ubuntu 14.04.2 LTS with Python 2.7.6 using akl's debug.py.

    @cfbearden
    Copy link
    Mannequin

    cfbearden mannequin commented May 1, 2015

    One further observation: the exception is thrown only if a breakpoint (apart from the call to pdb.set_trace) is set. If no breakpoint is set, the exception is not raised.

    @xdegaye
    Copy link
    Mannequin

    xdegaye mannequin commented Apr 29, 2018

    I can reproduce this bug with python 3.6.5 using akl's debug.py. The exception is now on 3.6.5:

    (Pdb) next
    --Call--
    Exception ignored in: <async_generator object _ag at 0x7fbca6e62320>
    Traceback (most recent call last):
      File "/usr/lib/python3.6/types.py", line 27, in _ag
      File "/usr/lib/python3.6/bdb.py", line 53, in trace_dispatch
      File "/usr/lib/python3.6/bdb.py", line 85, in dispatch_call
      File "/usr/lib/python3.6/pdb.py", line 251, in user_call
      File "/usr/lib/python3.6/pdb.py", line 351, in interaction
      File "/usr/lib/python3.6/pdb.py", line 1453, in print_stack_entry
      File "/usr/lib/python3.6/bdb.py", line 394, in format_stack_entry
    TypeError: 'NoneType' object is not callable

    bpo-33328 is a duplicate of this issue.

    @xdegaye
    Copy link
    Mannequin

    xdegaye mannequin commented May 8, 2018

    Nosying Serhiy as this post attempts to answer one of the questions raised in msg315588 in bpo-33328:

    Is it good that the debugger is enabled at the shutdown stage?

    This post has four different sections showing that the successive and incremental attempts at fixing the current code to have a destructor traced in the shutdown stage may lead to a segfault.

    Section 1 - Current status:
    ---------------------------
    A run with Python 3.6.5 of the following code proposed in the previous msg176993 gives now a different output and the C destructor is still not traced, but now it is not even called:

    1 class C:
    2 def __del__(self):
    3 print('deleted')
    4
    5 c = C()
    6 import pdb; pdb.set_trace()
    7 x = 1

    $ python test_destructor.py
    > test_destructor.py(7)<module>()
    -> x = 1
    (Pdb) step
    --Return--
    > test_destructor.py(7)<module>()->None
    -> x = 1
    (Pdb) step
    --Call--
    Exception ignored in: <async_generator object _ag at 0x7f3386d18320>
    Traceback (most recent call last):
      File "/usr/lib/python3.6/types.py", line 27, in _ag
      File "/usr/lib/python3.6/bdb.py", line 53, in trace_dispatch
      File "/usr/lib/python3.6/bdb.py", line 85, in dispatch_call
      File "/usr/lib/python3.6/pdb.py", line 251, in user_call
      File "/usr/lib/python3.6/pdb.py", line 351, in interaction
      File "/usr/lib/python3.6/pdb.py", line 1453, in print_stack_entry
      File "/usr/lib/python3.6/bdb.py", line 394, in format_stack_entry
    TypeError: 'NoneType' object is not callable

    Section 2 - Fix using PR 6730:
    ------------------------------
    PR 6730 in bpo-33446 fixes a bug in the pdb module that leaks a reference to frame->f_locals through its curframe_locals attribute.
    There is no major change when the current Python master branch is run with PR 6730, the destructor is still not traced and not called.

    Section 3 - Fix a bdb leak:
    ---------------------------
    After returning from the '__main__' module, the Bdb.bdb instance keeps a reference to the module through its botframe attribute and prevents the corresponding frame from being deallocated at the end of _PyEval_EvalCodeWithName() (the frame reference count is 2 instead of 1). The attached bdb_leak.diff patch fixes this, and when PR 6730 and bdb_leak.diff are run, the destructor is now traced but this fails with an exception in bdb:

    $ /home/xavier/src/python/master/python test_destructor.py
    > ./test_destructor.py(7)<module>()
    -> x = 1
    (Pdb) step
    --Return--
    > ./test_destructor.py(7)<module>()->None
    -> x = 1
    (Pdb) step
    Exception ignored in: <function C.__del__ at 0x7ff8ffdafd48>
    Traceback (most recent call last):
      File "test_destructor.py", line 3, in __del__
      File "test_destructor.py", line 3, in __del__
      File "/home/xavier/src/python/master/Lib/bdb.py", line 88, in trace_dispatch
      File "/home/xavier/src/python/master/Lib/bdb.py", line 115, in dispatch_line
      File "/home/xavier/src/python/master/Lib/pdb.py", line 262, in user_line
      File "/home/xavier/src/python/master/Lib/pdb.py", line 352, in interaction
      File "/home/xavier/src/python/master/Lib/pdb.py", line 1454, in print_stack_entry
      File "/home/xavier/src/python/master/Lib/bdb.py", line 544, in format_stack_entry
    ImportError: sys.meta_path is None, Python is likely shutting down

    Section 4 - Remove the lazy import:
    -----------------------------------
    The attached lazy_import.diff patch includes the changes in bdb_leak.diff patch and replaces the lazy imports causing the previous ImportError with imports at the start of the bdb module. Running test_destructor.py fails now with a segfault:

    $ /home/xavier/src/python/master/python test_destructor.py
    > ./test_destructor.py(7)<module>()
    -> x = 1
    (Pdb) step
    --Return--
    > ./test_destructor.py(7)<module>()->None
    -> x = 1
    (Pdb) step
    > ./test_destructor.py(3)__del__()
    -> print('deleted')
    Segmentation fault (core dumped)

    The gdb back trace is attached at gdb_backtrace.txt.

    Conclusion:
    -----------
    It seems that tracing should be prevented in the shutdown stage and that multiple little bugs have hidden that fact (when tracing is done with pdb).
    Not sure if tracing with PyEval_SetTrace() could be allowed in that stage.
    BTW coverage.py uses PyEval_SetTrace() and I have noticed that coverage.py does not trace either the destructor in test_destructor.py (nosying Ned).

    @xdegaye
    Copy link
    Mannequin

    xdegaye mannequin commented May 10, 2018

    Actually the segfault can be avoided by running the garbage collection before _PyState_ClearModules() in PyImport_Cleanup() and one can trace the C destructor with the attached patch global_destructors.diff applied on top of PR 6730.

    _PyState_ClearModules() clears the state->modules_by_index list and causes the readline module to be non-functional. The same problem occurs in test_create_at_shutdown_without_encoding() in test_io.py when it is run with the io module: the C destructor fails before hitting the "LookupError: unknown encoding: ascii" error message because the garbage collection is made after _PyState_ClearModules() and the _io module is non-functional (PyState_FindModule() returns NULL).

    So the destructors can be traced during finalization provided the tracer does not access any of the sys attributes that are set to None at the start of PyImport_Cleanup(). This is true for the globals of the modules that are only owned by sys.modules at the time 'weaklist' is about to be built in PyImport_Cleanup(). But it is not the case of '_ag' and that explains at last why the destructor was called and failed:
    '_ag' is a global of the types module which is owned (imported) by the inspect module, which is owned (imported) by the bdb module which is owned by PyThreadState through its c_profileobj member. So the types module, and all modules imported directly or indirectly by pdb, can only be cleared at the end of PyImport_Cleanup() when 'weaklist' is browsed for the last modules to clear. At that point any module may be non-functional and tracing must not be allowed.

    The global_destructors.diff patch ensures that no access is made by the pdb and bdb modules (but imported modules have not been checked) to the the sys attributes that are set to None at the start of PyImport_Cleanup():

    • Pdb.lookupmodule() accesses sys.path and is used to set breakpoints so we forbid setting breakpoint when sys.path is None.
    • All the values of sys.modules are set to None when 'weaklist' is built. This causes the lazy imports to fail and the patch removes them except the lazy import of pydoc in pdb.py because pydoc imports threading and when threading has been imported wait_for_thread_shutdown() in Py_FinalizeEx() runs the threading._shutdown() Python code which is traced (this happens in Python 3.6 too whenever threading is imported). Since it is annoying to systematically trace this function and pydoc is a heavy module, pdb.help() is disabled on Python finalization.

    @xdegaye
    Copy link
    Mannequin

    xdegaye mannequin commented May 10, 2018

    See also bpo-33458 that deals with the same problem when pdb is run as 'python -m pdb some_main.py' instead of by inserting a breakpoint with 'pdb.set_trace()'.

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    @gaogaotiantian
    Copy link
    Member

    Unable to reproduce with main (currently 3.13). Please reopen this if you can still repro with modern Python.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
    Projects
    None yet
    Development

    No branches or pull requests

    3 participants