crash printing non-local variable from nested subprogram

We have noticed that GDB would sometimes crash trying to print
from a nested function the value of a variable declared in an
enclosing scope. This appears to be target dependent, although
that correlation might only be fortuitious.  We noticed the issue
on x86_64-darwin, x86-vxworks6 and x86-solaris.  The investigation
was done on Darwin.

This is a new feature that was introduced by:

    commit 63e43d3aed
    Date:   Thu Feb 5 17:00:06 2015 +0100
    DWARF: handle non-local references in nested functions

We can reproduce the problem with one of the testcases that was
added with the patch (gdb.base/nested-subp1.exp), where we have...

    18 int
    19 foo (int i1)
    20 {
    21   int
    22   nested (int i2)
    23   {
    [...]
    27     return i1 * i2; /* STOP */
    28   }

...  After building the example program, and running until line 27,
try printing the value of "i1":

    % gdb gdb.base/nested-subp1
    (gdb) break foo.c:27
    (gdb) run
    Breakpoint 1, nested (i2=2) at /[...]/nested-subp1.c:27
    27          return i1 * i2; /* STOP */
    (gdb) p i1
    [1]    73090 segmentation fault  ../gdb -q gdb.base/nested-subp1

Ooops!

What happens is that, because the reference is non-local, we are trying
to follow the function's static link, which does...

    /* If we don't know how to compute FRAME's base address, don't give up:
       maybe the frame we are looking for is upper in the stace frame.  */
    if (framefunc != NULL
        && SYMBOL_BLOCK_OPS (framefunc)->get_frame_base != NULL
        && (SYMBOL_BLOCK_OPS (framefunc)->get_frame_base (framefunc, frame)
            == upper_frame_base))

... or, in other words, calls the get_frame_base "method" of
framefunc's struct symbol_block_ops data. This resolves to
the block_op_get_frame_base function.

Looking at the function's implementation, we see:

  struct dwarf2_locexpr_baton *dlbaton;
  [...]
  dlbaton = SYMBOL_LOCATION_BATON (framefunc);
  [...]
  result = dwarf2_evaluate_loc_desc (type, frame, start, length,
                                     dlbaton->per_cu);
                                     ^^^^^^^^^^^^^^^

Printing dlbaton->per_cu gives a value that seems fairly bogus for
a memory address (0x60). Because of it, dwarf2_evaluate_loc_desc
then crashes trying to dereference it.

What's different on Darwin compared to Linux is that the function's
frame base is encoded using the following form:

        .byte   0x40    # uleb128 0x40; (DW_AT_frame_base)
        .byte   0x6     # uleb128 0x6; (DW_FORM_data4)

... and so dwarf2_symbol_mark_computed ends up creating
a SYMBOL_LOCATION_BATON as a struct dwarf2_loclist_baton:

  if (attr_form_is_section_offset (attr)
      /* .debug_loc{,.dwo} may not exist at all, or the offset may be outside
         the section.  If so, fall through to the complaint in the
         other branch.  */
      && DW_UNSND (attr) < dwarf2_section_size (objfile, section))
    {
      struct dwarf2_loclist_baton *baton;
      [...]
      SYMBOL_LOCATION_BATON (sym) = baton;

However, if you look more closely at block_op_get_frame_base's
implementation, you'll notice that the function extracts the
symbol's SYMBOL_LOCATION_BATON as a dwarf2_locexpr_baton
(a DWARF _expression_ rather than a _location list_).
That's why we end up decoding the DLBATON improperly, and thus
pass a random dlbaton->per_cu when calling dwarf2_evaluate_loc_desc.

This works on x86_64-linux, because we indeed have the frame base
described using a different form:

        .uleb128 0x40   # (DW_AT_frame_base)
        .uleb128 0x18   # (DW_FORM_exprloc)

This patch fixes the issue by doing what we do for most (if not all)
other such methods: providing one implementation each for loc-list,
and loc-expr. Both implementations are nearly identical, so perhaps
we might later want to improve this. But this patch first tries to
fix the crash first, leaving the design issue for later.

gdb/ChangeLog:

        * dwarf2loc.c (locexpr_get_frame_base): Renames
        block_op_get_frame_base.
        (dwarf2_block_frame_base_locexpr_funcs): Replace reference to
        block_op_get_frame_base by reference to locexpr_get_frame_base.
        (loclist_get_frame_base): New function, near identical copy of
        locexpr_get_frame_base.
        (dwarf2_block_frame_base_loclist_funcs): Replace reference to
        block_op_get_frame_base by reference to loclist_get_frame_base.

Tested on x86_64-darwin (AdaCore testsuite), and x86_64-linux
(official testsuite).
This commit is contained in:
Joel Brobecker 2015-09-01 22:20:32 +02:00
parent 9b8b325a1f
commit 7d1c9c9b4c
2 changed files with 51 additions and 4 deletions

View File

@ -1,3 +1,14 @@
2015-09-20 Joel Brobecker <brobecker@adacore.com>
* dwarf2loc.c (locexpr_get_frame_base): Renames
block_op_get_frame_base.
(dwarf2_block_frame_base_locexpr_funcs): Replace reference to
block_op_get_frame_base by reference to locexpr_get_frame_base.
(loclist_get_frame_base): New function, near identical copy of
locexpr_get_frame_base.
(dwarf2_block_frame_base_loclist_funcs): Replace reference to
block_op_get_frame_base by reference to loclist_get_frame_base.
2015-09-19 Doug Evans <xdje42@gmail.com>
* ravenscar-thread.c (ravenscar_inferior_created): Replace

View File

@ -381,10 +381,11 @@ locexpr_find_frame_base_location (struct symbol *framefunc, CORE_ADDR pc,
*start = symbaton->data;
}
/* Implement the struct symbol_block_ops::get_frame_base method. */
/* Implement the struct symbol_block_ops::get_frame_base method for
LOC_BLOCK functions using a DWARF expression as its DW_AT_frame_base. */
static CORE_ADDR
block_op_get_frame_base (struct symbol *framefunc, struct frame_info *frame)
locexpr_get_frame_base (struct symbol *framefunc, struct frame_info *frame)
{
struct gdbarch *gdbarch;
struct type *type;
@ -421,7 +422,7 @@ block_op_get_frame_base (struct symbol *framefunc, struct frame_info *frame)
const struct symbol_block_ops dwarf2_block_frame_base_locexpr_funcs =
{
locexpr_find_frame_base_location,
block_op_get_frame_base
locexpr_get_frame_base
};
/* Implement find_frame_base_location method for LOC_BLOCK functions using
@ -436,13 +437,48 @@ loclist_find_frame_base_location (struct symbol *framefunc, CORE_ADDR pc,
*start = dwarf2_find_location_expression (symbaton, length, pc);
}
/* Implement the struct symbol_block_ops::get_frame_base method for
LOC_BLOCK functions using a DWARF location list as its DW_AT_frame_base. */
static CORE_ADDR
loclist_get_frame_base (struct symbol *framefunc, struct frame_info *frame)
{
struct gdbarch *gdbarch;
struct type *type;
struct dwarf2_loclist_baton *dlbaton;
const gdb_byte *start;
size_t length;
struct value *result;
/* If this method is called, then FRAMEFUNC is supposed to be a DWARF block.
Thus, it's supposed to provide the find_frame_base_location method as
well. */
gdb_assert (SYMBOL_BLOCK_OPS (framefunc)->find_frame_base_location != NULL);
gdbarch = get_frame_arch (frame);
type = builtin_type (gdbarch)->builtin_data_ptr;
dlbaton = SYMBOL_LOCATION_BATON (framefunc);
SYMBOL_BLOCK_OPS (framefunc)->find_frame_base_location
(framefunc, get_frame_pc (frame), &start, &length);
result = dwarf2_evaluate_loc_desc (type, frame, start, length,
dlbaton->per_cu);
/* The DW_AT_frame_base attribute contains a location description which
computes the base address itself. However, the call to
dwarf2_evaluate_loc_desc returns a value representing a variable at
that address. The frame base address is thus this variable's
address. */
return value_address (result);
}
/* Vector for inferior functions as represented by LOC_BLOCK, if the inferior
function uses DWARF location list for its DW_AT_frame_base. */
const struct symbol_block_ops dwarf2_block_frame_base_loclist_funcs =
{
loclist_find_frame_base_location,
block_op_get_frame_base
loclist_get_frame_base
};
/* See dwarf2loc.h. */