This is the mail archive of the gdb-prs@sourceware.org mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[Bug backtrace/19927] Python unwinder API should be resilient to recursion and frame cache reinitialization


https://sourceware.org/bugzilla/show_bug.cgi?id=19927

--- Comment #3 from cvs-commit at gcc dot gnu.org <cvs-commit at gcc dot gnu.org> ---
The master branch has been updated by Pedro Alves <palves@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=f245535cf583ae4ca13b10d47b3c7d3334593ece

commit f245535cf583ae4ca13b10d47b3c7d3334593ece
Author: Pedro Alves <palves@redhat.com>
Date:   Mon Sep 5 18:41:38 2016 +0100

    Fix PR19927: Avoid unwinder recursion if sniffer uses calls parse_and_eval

    This fixes the problem exercised by Kevin's test at:

     https://sourceware.org/ml/gdb-patches/2016-08/msg00216.html

    This was originally exposed by the OpenJDK Python-based unwinder.

    If an unwinder attempts to call parse_and_eval from within its
    sniffing method, GDB's unwinding machinery enters infinite recursion.
    However, parse_and_eval is a pretty reasonable thing to call, because
    Python/Scheme-based unwinders will often need to read globals out of
    inferior memory.  The recursion happens because:

    - get_current_frame() is called soon after the target stops.

    - current_frame is NULL, and so we unwind it from the sentinel frame
      (which is special and has level == -1).

    - We reach get_prev_frame_if_no_cycle, which does cycle detection
      based on frame id, and thus tries to compute the frame id of the new
      frame.

    - Frame id computation requires an unwinder, so we go through all
      unwinder sniffers trying to see if one accepts the new frame (the
      current frame).

    - the unwinder's sniffer calls parse_and_eval().

    - parse_and_eval depends on the selected frame/block, and if not set
      yet, the selected frame is set to the current frame.

    - get_current_frame () is called again.  current_frame is still NULL,
      so ...

    - recurse forever.


    In Kevin's test at:

     https://sourceware.org/ml/gdb-patches/2016-08/msg00216.html

    gdb doesn't recurse forever simply because the Python unwinder
    contains code to detect and stop the recursion itself.  However, GDB
    goes downhill from here, e.g., by showing the sentinel frame as
    current frame (note the -1):

        Breakpoint 1, ccc (arg=<unavailable>) at py-recurse-unwind.c:23
        23      }
        (gdb) bt
        #-1 ccc (arg=<unavailable>) at py-recurse-unwind.c:23
        Backtrace stopped: previous frame identical to this frame (corrupt
stack?)

    That "-1" frame level comes from this:

          if (catch_exceptions (current_uiout, unwind_to_current_frame,
                            sentinel_frame, RETURN_MASK_ERROR) != 0)
        {
          /* Oops! Fake a current frame?  Is this useful?  It has a PC
                 of zero, for instance.  */
          current_frame = sentinel_frame;
        }

    which is bogus.  It's never correct to set the current frame to the
    sentinel frame.  The only reason this has survived so long is that
    getting here normally indicates something wrong has already happened
    before and we fix that.  And this case is no exception -- it doesn't
    really matter how precisely we managed to get to that bogus code (it
    has to do with the the stash), because anything after recursion
    happens is going to be invalid.

    So the fix is to avoid the recursion in the first place.

    Observations:

     #1 - The recursion happens because we try to do cycle detection from
          within get_prev_frame_if_no_cycle.  That requires computing the
          frame id of the frame being unwound, and that itself requires
          calling into the unwinders.

     #2 - But, the first time we're unwinding from the sentinel frame,
          when we reach get_prev_frame_if_no_cycle, there's no frame chain
          at all yet:

          - current_frame is NULL.
          - the frame stash is empty.

    Thus, there's really no need to do cycle detection the first time we
    reach get_prev_frame_if_no_cycle, when building the current frame.

    So we can break the recursion by making get_current_frame call a
    simplified version of get_prev_frame_if_no_cycle that results in
    setting the current_frame global _before_ computing the current
    frame's id.

    But, we can go a little bit further.  As there's really no reason
    anymore to compute the current frame's frame id immediately, we can
    defer computing it to when some caller of get_current_frame might need
    it.  This was actually how the frame id was computed for all frames
    before the stash-based cycle detection was added.  So in a way, this
    patch reintroduces the lazy frame id computation, but unlike before,
    only for the case of the current frame, which turns out to be special.

    This lazyness, however, requires adjusting
    gdb.python/py-unwind-maint.exp, because that assumes unwinders are
    immediately called as side effect of some commands.  I didn't see a
    need to preserve the behavior expected by that test (all it would take
    is call get_frame_id inside get_current_frame), so I adjusted the
    test.

    gdb/ChangeLog:
    2016-09-05  Pedro Alves  <palves@redhat.com>

        PR backtrace/19927
        * frame.c (get_frame_id): Compute the frame id if not computed
        yet.
        (unwind_to_current_frame): Delete.
        (get_current_frame): Use get_prev_frame_always_1 to get the
        current frame and assert that that always succeeds.
        (get_prev_frame_if_no_cycle): Skip cycle detection if returning
        the current frame.

    gdb/testsuite/ChangeLog:
    2016-09-05  Pedro Alves  <palves@redhat.com>

        PR backtrace/19927
        * gdb.python/py-unwind-maint.exp: Adjust tests to not expect that
        unwinders are immediately called as side effect of "source" or
        "disable unwinder" commands.
        * gdb.python/py-recurse-unwind.exp: Remove setup_kfail calls.

-- 
You are receiving this mail because:
You are on the CC list for the bug.

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]