Bug 904252 - VTA causes empty/garbage debug_loc entry
Summary: VTA causes empty/garbage debug_loc entry
Keywords:
Status: CLOSED CURRENTRELEASE
Alias: None
Product: Fedora
Classification: Fedora
Component: gcc
Version: 18
Hardware: Unspecified
OS: Unspecified
unspecified
unspecified
Target Milestone: ---
Assignee: Alexandre Oliva
QA Contact: Fedora Extras Quality Assurance
URL:
Whiteboard:
Depends On:
Blocks:
TreeView+ depends on / blocked
 
Reported: 2013-01-25 21:00 UTC by Josh Stone
Modified: 2013-11-08 08:02 UTC (History)
3 users (show)

Fixed In Version:
Doc Type: Bug Fix
Doc Text:
Clone Of:
Environment:
Last Closed: 2013-11-08 08:02:07 UTC
Type: Bug
Embargoed:


Attachments (Terms of Use)


Links
System ID Private Priority Status Summary Last Updated
GNU Compiler Collection 56154 0 'P2' 'RESOLVED' '[4.7 Regression] Bad .debug_loc generated for some code' 2019-11-12 16:37:41 UTC

Description Josh Stone 2013-01-25 21:00:28 UTC
Description of problem:
I'm extracting this issue from bug 903804.  In the debuginfo for glibc's accept4(), the second parameter 'addr' references a location list which is empty and followed by UNUSED GARBAGE (as called by eu-readelf).  In my own testing I found that toggling VTA off fixes this behavior.

Mark got a nice reproducer in bug 903804 comment 6, which I've reduced somewhat further:

typedef union { int a, b; } foo_t;
int
foo (int fd, foo_t x)
{
  int result = x.a != 0;
  if (fd != 0)
    result = x.a == 0;
  return result;
}

Version-Release number of selected component (if applicable):
gcc-4.7.2-8.fc18.x86_64

Steps to Reproduce:

With VTA, the first location entry is empty, followed by garbage:
> $ gcc -Wall -fvar-tracking-assignments -gdwarf-4 -O1 -c foo.c
> $ eu-readelf --debug-dump=loc foo.o
> 
> DWARF section [ 7] '.debug_loc' at offset 0x186:
>  [     0]  empty list
>  [    10]  <UNUSED GARBAGE> ... 40 bytes ...
>  [    38]  .text+000000000000000000 <foo>...text+0x0000000000000014 <foo+0x14> [   0] breg4 0
>                                                   [   2] const1u 32
>                                                   [   4] shl
>                                                   [   5] lit0
>                                                   [   6] ne
>                                                   [   7] const1u 255
>                                                   [   9] and
>                                                   [  10] stack_value
>            .text+0x0000000000000014 <foo+0x14>...text+0x0000000000000016 [   0] reg0

Without VTA, the first location looks fine:
> gcc -Wall -fno-var-tracking-assignments -gdwarf-4 -O1 -c foo.c
> $ eu-readelf --debug-dump=loc foo.o
> 
> DWARF section [ 7] '.debug_loc' at offset 0x186:
>  [     0]  .text+000000000000000000 <foo>...text+0x0000000000000002 <foo+0x2> [   0] reg4
>            .text+0x0000000000000002 <foo+0x2>...text+0x0000000000000016 [   0] reg4
>                                                   [   1] piece 4
>  [    38]  .text+0x0000000000000008 <foo+0x8>...text+0x0000000000000011 <foo+0x11> [   0] reg0
>            .text+0x0000000000000014 <foo+0x14>...text+0x0000000000000014 <foo+0x14> [   0] reg0

What I find really interesting is that range 0-0x38 is almost identical with and without VTA, including the supposed garbage:
> $ eu-readelf -x .debug_loc foo.vta.o foo.novta.o
> 
> foo.vta.o:
> 
> 
> Hex dump of section [7] '.debug_loc', 120 bytes at offset 0x186:
>   0x00000000 00000000 00000000 00000000 00000000 ................
>   0x00000010 01005400 00000000 00000016 00000000 ..T.............
>   0x00000020 00000003 00549304 00000000 00000000 .....T..........
>   0x00000030 00000000 00000000 00000000 00000000 ................
>   0x00000040 14000000 00000000 0b007400 08202430 ..........t.. $0
>   0x00000050 2e08ff1a 9f140000 00000000 00160000 ................
>   0x00000060 00000000 00010050 00000000 00000000 .......P........
>   0x00000070 00000000 00000000                   ........
> 
> foo.novta.o:
> 
> 
> Hex dump of section [7] '.debug_loc', 110 bytes at offset 0x186:
>   0x00000000 00000000 00000000 02000000 00000000 ................
>   0x00000010 01005402 00000000 00000016 00000000 ..T.............
>   0x00000020 00000003 00549304 00000000 00000000 .....T..........
>   0x00000030 00000000 00000000 08000000 00000000 ................
>   0x00000040 11000000 00000000 01005014 00000000 ..........P.....
>   0x00000050 00000014 00000000 00000001 00500000 .............P..
>   0x00000060 00000000 00000000 00000000 0000     ..............

The only difference from 0-0x38 are bytes 8 and 0x13 which both changed from 00 with VTA to 02 without VTA.  It would appear that "garbage" was in fact meaningful data.

My best guess is that VTA makes it so the first range is 0-0 instead of 0-2.  Then eu-readelf sees that 0-0 as an empty entry, and doesn't process anything more until the next loc 0x38.

FWIW binutils readelf reacts similarly:
> $ readelf --debug-dump=loc foo.vta.o
> Contents of the .debug_loc section:
> 
>     Offset   Begin    End      Expression
>     00000000 <End of list>
> readelf: Warning: There is a hole [0x10 - 0x38] in .debug_loc section.
>     00000038 0000000000000000 0000000000000014 (DW_OP_breg4 (rsi): 0; DW_OP_const1u: 32; DW_OP_shl; DW_OP_lit0; DW_OP_ne; DW_OP_const1u: 255; DW_OP_and; DW_OP_stack_value)
>     00000038 0000000000000014 0000000000000016 (DW_OP_reg0 (rax))
>     00000038 <End of list>

Comment 1 Alexandre Oliva 2013-01-29 20:32:23 UTC
Thanks, I see what's going on here.

With var tracking enabled, SRA emits a location note for the x$a now-separate scalar at the very beginning of the function, which happens to be at the very beginning of the text segment for this compilation unit.

dwarf2out does not discard this empty range (it might contain useful entry-point debug information), but because the function is at the very beginning of the text segment, the range interval [.LVL0-.Ltext0, .LVL0-.Ltext0) evaluates to [0, 0), which happens to be the marker for end of location list.

Discarding the empty range in dwarf2out is one possible fix, although we have to be careful about multiple labels at the same address, and even then we might be throwing away useful debug information.  Emitting padding after .Ltext0 is another option, although a bit wasteful.  It doesn't seem to be possible to use a base address other than the CU text base address for ranges.  One less wasteful possibility is to reorder the entries so that, if we happen to have an empty range right in the beginning of the CU, it will be the last rather than the first entry for a variable, so that, even if it's completely disregarded, we don't lose much.

Comment 2 Josh Stone 2013-01-29 20:51:00 UTC
(In reply to comment #1)
> One less wasteful possibility is to reorder the entries so that, if
> we happen to have an empty range right in the beginning of the CU, it will
> be the last rather than the first entry for a variable, so that, even if
> it's completely disregarded, we don't lose much.

IMO, it's never going to be useful to output real data in a form that looks like an end marker.  Data that all tools will disregard has lost all meaning.

According to the DWARF4 spec, the address ranges are allowed to overlap.  So maybe a [0,0) could just be kludged into a [0,1) range?  If this is legitimate entry-point data, I expect it should be perfectly valid for the first byte.

Comment 3 Mark Wielaard 2013-01-29 21:06:58 UTC
(In reply to comment #1)
> dwarf2out does not discard this empty range (it might contain useful
> entry-point debug information), but because the function is at the very
> beginning of the text segment, the range interval [.LVL0-.Ltext0,
> .LVL0-.Ltext0) evaluates to [0, 0), which happens to be the marker for end
> of location list.

Maybe I am missing what is being output precisely. But for the end address of an location list entry: "It marks the first address past the end of the address range over which the location is valid. The ending address must be greater than or equal to the beginning address." (DWARF4 2.6.2)

So shouldn't this be output as [0, 1)?

Comment 4 Josh Stone 2013-01-29 21:08:04 UTC
BTW, I also wanted to share that it's possible to exploit bug 896741 to more clearly see what location list was intended.  So the VTA case with -pg -mfentry gives this:

> DWARF section [ 8] '.debug_loc' at offset 0x18a:
>  [     0]  .text+0x0000000000000005 <foo+0x5>...text+0x0000000000000005 <foo+0x5> [   0] reg4
>            .text+0x0000000000000005 <foo+0x5>...text+0x000000000000001b [   0] reg4
>                                                   [   1] piece 4

Comment 5 Mark Wielaard 2013-01-29 21:13:02 UTC
(In reply to comment #1)
> It doesn't seem to be possible to use a base address other than the CU text
> base address for ranges.

Again I might be missing where this isn't possible. But it is possible according to the DWARF4 spec (2.6.2 again) to define a base address selection entry:

A base address selection entry consists of:
1. The value of the largest representable address offset (for example, 0xffffffff when the size of an address is 32 bits).
2. An address, which defines the appropriate base address for use in interpreting the beginning and ending address offsets of subsequent entries of the location list.
A base address selection entry affects only the list in which it is contained.

Comment 6 Alexandre Oliva 2013-01-29 21:29:33 UTC
Mark, what I  meant it wasn't possible to choose an alternate base address for all location lists.  That is indeed possible on a per-list basis, but starting on Dwarf3 (I had completely forgotten about this possibility, if I ever knew about it :-)  So that's one more possibility we can consider.

Using the [0,1) range sounds reasonable, but that might require a bit of special casing.  Unless...  I'm now thinking whether there's ever a situation in which the value of a var_location note is invalidated without an intervening instruction; it could be that every such note could imply a non-empty range.


Josh, you don't have to exploit any other bug to see what we meant to emit as the location list.  If looking at the -S -dA asm output doesn't sound appealing to you, just arranging for another function to be output before the one at hand will do.  The problem only (potentially) affects the first function output in a translation unit.

Comment 7 Jakub Jelinek 2013-01-29 21:35:25 UTC
Sometimes we know some range is empty (there are only notes in between, no real insns, then typically it should be even the same .LVL* label), but sometimes we don't and can't know.  Consider if the first insn in the first .text function is
asm ("# comment");, gcc doesn't know the size of that insn(s), and if it turns out to be zero, the range covering just that stmt might still be empty.

For the first function in the CU, at least for dwarf3+ or !dwarf_strict, we could check if any of the location lists could start with possible empty ranges, and if so, either force use of DW_AT_ranges on the DW_TAG_compile_unit and use DW_AT_low_pc 0 and absolute location lists, or consider adding base address selection entries to all first function's location lists that might start at the first byte.

Comment 8 Alexandre Oliva 2013-01-29 22:23:46 UTC
I'm afraid until we implement statement frontiers we can't really extend the range of location notes, not even at the entry point.  That's because, when we stop at it, we'll be already past any optimized-away instructions that might have survived only in var_location notes, if at all, which may invalidate the entry point bindings.  To make it concrete, consider:

void foo(int i) {
  i++;
}

The increment will surely be optimized away, the function will become something like:

(var_location i (reg firstargreg))
(deleted_insn)
(var_location i (plus (reg firstargreg) (const_int 1)))
(return)

If we stop at the entry point, we'll be at the return instruction, which is past the second binding for i.  Using the initial binding at that point would be technically incorrect, although it would give you the correct value for i at the entry point.  However, using the second binding, that covers that address in debug info, would give you a different value, so making both active at that address would be wrong.

This is the sort of problem that stmt frontiers would address, but until we have that, we don't get to examine at run time the value of variables at entry points in the presence of this sort of optimization.

So, no, we can't extend the range of the entry-point value, not even by a single byte :-(

Comment 9 Jakub Jelinek 2013-01-30 09:33:19 UTC
void
foo (int x)
{
  asm ("");
  x++;
}

with -g -O2 is an example of testcase where gcc right now also generates the 0, 0 at the beginning of location list, and where gcc can't know that the first range (.LVL0 .. .LVL1) is empty.

As for the patch that breaks the #c0 testcase, it was
http://gcc.gnu.org/ml/gcc-patches/2011-06/msg01067.html
where you can find the rationale for it.

Anyway, to detect the case of (potentially) empty ranges, we only need to check the first function, don't care about have_multiple_function_sections, ignore NOTE, BARRIER, CODE_LABEL, asm_noperands, insn with ASM_INPUT or bare USE patterns (i.e. for those assume minimum length 0), and for other insns #ifndef HAVE_attr_length just assume non-zero length, for #ifdef HAVE_attr_length we could use e.g. get_attr_min_length.  We could mark somehow loc lists with potentially empty ranges at the beginning of first function (e.g.
struct var_loc_list_def has for 64-bit host 32 bits left in padding), and then in dwarf2out_finish use that info (perhaps just for dwarf_version >= 3 || !dwarf_strict), either to force DW_AT_ranges for the CU and 0 DW_AT_low_pc (a few bytes added to .debug_ranges section, roughly the same size in DW_TAG_compile_unit, no growth in .debug_loc, but potentially many new relocations against .debug_loc section), or by emitting base location entry to each location list containing such entries (and use then a base of 0 or base of .Ltext0 - 1 or something similar).

Comment 10 Jakub Jelinek 2013-11-08 08:02:07 UTC
Josh has recently pinged me about the original testcase from #c0 still producing:
gcc -g -O1 rh904252.c -c; readelf -wo rh904252.o
Contents of the .debug_loc section:

    Offset   Begin    End      Expression
    00000000 <End of list>
readelf: Warning: There is a hole [0x10 - 0x38] in .debug_loc section.
    00000038 0000000000000000 0000000000000014 (DW_OP_breg4 (rsi): 0; DW_OP_const1u: 32; DW_OP_shl; DW_OP_lit0; DW_OP_ne; DW_OP_const1u: 255; DW_OP_and; DW_OP_stack_value)
    00000055 0000000000000014 0000000000000016 (DW_OP_reg0 (rax))
    00000068 <End of list>

but that doesn't look like a GCC bug anymore, but rather readelf/eu-readelf bug.
The real gcc bug was that it could generate something like:
        .quad   .LVL0-.Ltext0   # Location list begin address (*.LLST0)
        .quad   .LVL0-.Ltext0   # Location list end address (*.LLST0)
for the start/end (or .LVL0 and .LVL1 with say asm (""); in between the two labels where gcc doesn't know the labels are at the same address).
GCC 4.8 generates instead:
        .quad   .LVL0   # Location list begin address (*.LLST0)
        .quad   .LVL0   # Location list end address (*.LLST0)
in this case (it forcefully doesn't use the relative addresses, because they indeed could be zero).  But we assume (IMHO rightfully so) that text addresses aren't equal to zero, thus .LVL0 is not zero, it is indeed an empty range,
but http://gcc.gnu.org/ml/gcc-patches/2011-06/msg01067.html explains the rationale for outputting that.  The reason why readelf complains here is that
you are running it on an object file rather than executable or shared library,
and either readelf ignores the relocations at that spot, or just thinks it might be zero.  For relocatable objects, it should just assume if there is a relocation against either of the addresses, then it is not a location list terminator.


Note You need to log in before you can comment on or make changes to this bug.