[Dwarf-Discuss] Location list ranges vs. containing lexical block DW_AT_ranges

Roland McGrath roland at redhat.com
Fri Apr 9 17:41:53 PDT 2010

> Q1: What does it mean for a range to extend beyond the scope of the 
> variable?
> Q2: What does it mean for a range to begin before the beginning of the 
> scope of a variable?


	static void foo (const int x) { ... }
	bar (const int y)
	  foo (x);

Say foo is inlined.  One possibility is to emit the same location list for
"x" as for "y".  (This is even a space-saver if that works by using the
same .debug_loc offset in both DW_AT_location attributes).  Certainly it
makes no abstract sense to talk about "x" having a value or location
outside the set of PCs that constitute "foo".  But it might make concrete
sense in the debugging context to say that foo's "x" is just a source-level
alias for the same program element that's bar's "y".

I'm not especially advocating doing this, but it is a case that makes a
certain kind of sense of what seems at first blush like a logical

> The only way that I can image that the range starts before the scope is 
> if the variable is assigned from another variable with a larger scope 
> and the two variables happen to be allocated to the same register! In 
> this case there is no explicit assignment to begin the lifetime--it 
> happens implicitly for "free".

Right.  My example might be thought of as a subset of this case.
But it's cast in a different light that makes ...

> Q3: What does it mean for a range to begin *and* end outside of the 
> scope of the variable. For example, the

... sensical.

> This cannot be correct. Just because the value of the variable is 
> available in reg1 in 12..22 and also in 28..35 does not mean that the 
> value is available in reg1 in 23..27. But that is exactly what this 
> description seems to imply.

I quite agree--at least in the case where reg1 is in fact reused for other
purposes in between.  In that sort of case, the location list taken on its
own contains lies, and I don't think that's acceptable.

> I think I see where you are going--if the part of a range that is 
> outside of the containing scope must be (actively) ignored by a 
> debugger, then *any* description for what is happening outside that 
> scope is acceptable (neither right nor wrong, because irrelevant).

I grok that logic too, but I don't accept it.  (I think we are in agreement
here.)  I think the fine-grained "local" information like a location list
should not contain any lies at all, and that you should not have to reason
about the higher-level picture to glean a "true" subset of that information.

> How can a change to a variable possibly occur when it is not in scope? 
> My answer--it can't! The place that held the value of the variable when 
> it was in scope can surely change, but not because the variable change 
> but because some totally irrelevant use of the place is occurring.
> One also has to wonder how GDB (or any other debugger) will implement 
> watchpoints on a variable allocated in a register. 

I'd rather not digress into that sort of implementation detail here.
A "watchpoint" can mean anything from fine-grained hardware-assisted
tracking to brute-force methods like single-step and checking all
register and memory values of interest after every instruction.
It's outside the scope of DWARF and of this list to consider what such
methods are or aren't used in practice.  I'd like to concentrate just
on what is true or not true about the correspondence between the compiled
code and the semantic program.

I concur with your "it can't!" analysis only in the case where the register
or memory location was reused for a different purpose in any portion of the
code that's disjoint with the PC set that constitutes the variable's scope.
If a particular location corresponds only to the given variable throughout
a PC set even though the variable's scope exists only in a subset of that
PC set, then it is very much worthwhile for a debugger to consider that
location as of interest in tracking that variable throughout the entire
larger PC set.


	  int x = init1;
	  int y = init2;

Now imagine that the code corresponding to these two blocks gets
interleaved (e.g. when the two "..." bits don't interact at all).  
So code is:

0	... code outside the blocks ...
10	mov init1, fp[-4]
20	mov init2, fp[-8]
30	... 1st computation from the "x" block ...
40	... 1st computation from the "y" block ...
50	... 2nd computation from the "x" block ...
60	... 2nd computation from the "y" block ...
70	... code outside the blocks ...

The first DW_TAG_lexical_block has DW_AT_ranges of 10-20,30-40,50-60.
The second DW_TAG_lexical_block has DW_AT_ranges of 20-30,40-50,60-70.
Hence, 40 is outside the scope of "x" and 50 is outside the scope of "y".

Throughout 0-80 there is no place fp[-4] is used for anything other than
"x" and no place fp[-8] is used for anything other than "y".

If the location lists only match the scope, then "x" will have:

10-20 DW_OP_fbreg -4
30-40 DW_OP_fbreg -4
50-60 DW_OP_fbreg -4

For debugging tasks, it's clearly far more useful for it to be:

0-80 DW_OP_fbreg -4

Let's say 0-80 is the entire PC set of the containing DW_TAG_subprogram.
In that case, "x" would just have a single exprloc rather than using a
location list at all.

This lets the debugger know it can monitor the fp[-4] memory word
throughout the life of the whole frame.  Now if some code anywhere (via
compiler bugs, wild pointers, whatever) touches that memory word, we'll
catch it.  A location list pruned strictly to the scope of "x" instead
tells the debugger that it can't assume fp[-4] is relevant to "x" in
e.g. the 40-50 range.  If those instructions are miscompiled to clobber
fp[-4] when they don't intend to, we fail to catch that.  If something else
like a buffer overrun in an interrupt/signal handler implicitly called from
PC=40 clobbers it, we fail to catch that.

The same rationale applies for locations involving only register values,
even though that is a much less realistic example.


More information about the Dwarf-Discuss mailing list