(Petr is on vacation, so it will most likely be at least a few weeks before anyone starts to work on this.)
Jakub was interested in sampling some DWARF data to compare what one compiler vs another is doing with some broad statistics of semantic interest. The first particular thing to measure is how much location/value information we are getting for variables and parameters.
This is not at all an error check, but I think it fits well enough into dwarflint as a special option to request statistics reports (in future perhaps several different kinds of things) either in addition to or instead of the normal warnings/errors. The first kind of statistic of interest is in stuff that I think dwarflint looks at already for some of its checks.
So, it would go something like this:
Consider each DW_TAG_variable or DW_TAG_formal_parameter that might have a location. That is, ignore variables with DW_AT_declaration and ignore the formal_parameter children of subprograms with DW_AT_declaration. Look at all the rest, and tally. (Double-check the cases with no DW_AT_location or DW_AT_const_value at all to see if there is some other exclusion from "might have a location" that I'm overlooking at the moment.) For each, compute what percentage of the variable's scope has locations or values.
An extern global has DW_AT_external and no location at all, but is not missing any info. So exclude those from the tally.
Globals (with DW_AT_external) and statics (without) are identified by having a non-list DW_AT_location that is a singleton DW_OP_addr expression. Perhaps (optionally?) exclude these from the tally entirely, so they don't dilute the cumulative percentages. Every compiler always just emits those locations regardless of optimization or debuginfo fanciness, so for comparing compilers it is probably more meaningful to measure only among the pool of variables with dynamic extent.
If it has DW_AT_const_value, then call that 100%. If it has a non-list DW_AT_location with a nonempty expression, also call that 100%. If it has no DW_AT_location or a non-list DW_AT_location with an empty expression, call that 0%.
Otherwise, it's a location list. So, first find the "scope" ranges set for this DIE. That is, if the DIE itself has a DW_AT_start_scope that is a rangelistptr, then exactly that is the set. If the DIE itself has DW_AT_{ranges,high_pc} then that's the set (but it won't). Otherwise, look back up parent DIEs until one has ranges/high_pc. If the variable DIE has a DW_AT_start_scope that is a constant, then exclude the portion of the range before it (see DWARFv4 3.3.8.2 item 11).
If the location list covers any address bytes outside the "scope" set, then exclude those portions from the location list set for further consideration. I think dwarflint might already have a warning about that happening, or if not perhaps it should (though perhaps it is also an OK thing in the data, it remains unclear).
Exclude any location list entries whose expression is empty. (You might already have a warning about those too, since they are superfluous entries to have.)
Finally, count up the cumulative bytes covered by the scope set and those covered by the location set. Tally the ratio of those two counts as a percentage. Perhaps produce a scatter plot with x axis the location/scope rounded to an integer percentage and y axis the percentage of the DIEs considered whose ratio is x. And perhaps say min/max/avg(/median?) ratios seen.
Another variant would be to also tally what portion of available locations is mutable vs immutable (or distribution of the ratios, or whatever). That is, DW_AT_const_value is immutable. A location expression is immutable if it ends in DW_OP_implicit_value or DW_OP_stack_value. If an expression uses DW_OP_{,bit_}piece, then it can be partially mutable and partially immutable. You can probably just choose arbitrarily to count those on one side or the other, or perhaps tally them as a separate third statistic.
Jakub can say better than I which statistical analyses he is most interested in seeing.
Thanks, Roland
On Tue, Jul 20, 2010 at 06:58:30PM -0700, Roland McGrath wrote:
(Petr is on vacation, so it will most likely be at least a few weeks before anyone starts to work on this.)
Thanks, yeah, that's roughly what I'm looking for, something better than counting the number of DW_AT_const_value and DW_AT_location attributes in .debug_info section and .debug_loc sizes, because some changes might e.g. make .debug_loc smaller, yet still result in better variable location coverage, etc. Also, such a tool would allow to quantitatively compare the effect of -fvar-tracking-assignments or similar options on larger codebases, so if anyone asks about the advantages of VTA, we can provide some nice numbers.
The output can be either as some summaries percentage of vars with location, percentage of coverage of code bytes where var is in scope, or could be e.g. dumping all the vars (name, locus, size_of_scope_ranges, size_of_ranges_where_it_has_valid_location, the same with mutable location) one at a line, or something similar.
Jakub
21.07.2010 03:58, Roland McGrath wrote:
Jakub was interested in sampling some DWARF data to compare what one compiler vs another is doing with some broad statistics of semantic interest. The first particular thing to measure is how much location/value information we are getting for variables and parameters.
This is now on the dwarf branch. Use like this: dwarflint --check=locstats binary
(Double-check the cases with no DW_AT_location or DW_AT_const_value at all to see if there is some other exclusion from "might have a location" that I'm overlooking at the moment.)
I'm getting a lot of misses from the following areas: - artificial variables/parameters (but some of them do have location) - descendants of DIEs that have DW_AT_inlined == "inlined" or "declared_inlined" (but many of them do have location) - descendants of DIEs that are DW_TAG_inlined_subroutine (but there's so much of them that that's no wonder. Ignoring those cuts off about 90% of variable/parameter DIEs)
There's a bunch of options for dumping and ignoring groups of dies, so one can e.g. do this: dwarflint --check=locstats --locstats:ignore=artificial,inlined dwarflint --check=locstats --locstats:dump=no_coverage
The dumps (that's not the statistics-gathering part, just the debugging one) list the whole DIE stack so that one has some context to ponder:
dumping no_coverage DIE DIE 2730c compile_unit producer="GNU C++ 4.4.4 20100630 (Red Hat 4.4.4-10)" language=C_plus_plus name="../../elfutils/dwarflint/dwarflint.cc" comp_dir="... low_pc=0 entry_pc=0 ranges=<0x407790-0x40872e,0x408730-0x4087fb,... stmt_list=[{11 dirs}, {432 line entries}] DIE 373bf subprogram specification=[0x2843e] inline=declared_inlined DIE 373d3 lexical_block DIE 373d5 variable name="__ret" decl_file="/usr/lib/gcc/... decl_line=122 type=[0x2a2ce]
Globals (with DW_AT_external) and statics (without) are identified by having a non-list DW_AT_location that is a singleton DW_OP_addr expression. Perhaps (optionally?) exclude these from the tally entirely, so they don't dilute the cumulative percentages.
That's --locstats:ignore=single-addr
Otherwise, it's a location list. So, first find the "scope" ranges set for this DIE. That is, if the DIE itself has a DW_AT_start_scope that is a rangelistptr, then exactly that is the set. If the DIE itself has DW_AT_{ranges,high_pc} then that's the set (but it won't). Otherwise, look back up parent DIEs until one has ranges/high_pc. If the variable DIE has a DW_AT_start_scope that is a constant, then exclude the portion of the range before it (see DWARFv4 3.3.8.2 item 11).
I used c++/dwarf header for getting the scope. Does that/will that take into account the DW_AT_start_scope adjustments, or is that something that the client should take care of themselves?
Finally, count up the cumulative bytes covered by the scope set and those covered by the location set. Tally the ratio of those two counts as a percentage. Perhaps produce a scatter plot with x axis the location/scope rounded to an integer percentage and y axis the percentage of the DIEs considered whose ratio is x. And perhaps say min/max/avg(/median?) ratios seen.
Currently it emits a table like this: cov% samples cumul 0..10 44269/52% 44269/52% 11..20 665/0% 44934/53% 21..30 910/1% 45844/54% 31..40 1273/1% 47117/55% 41..50 1425/1% 48542/57% 51..60 1326/1% 49868/59% 61..70 2102/2% 51970/61% 71..80 2317/2% 54287/64% 81..90 3549/4% 57836/68% 91..100 26684/31% 84520/100%
One can adjust the way it's tabulated by setting the stops manually. The table above is produced with a default rule of --locstats:tabulate=10:10 (first stop at 10, then each 10 percent points). E.g. --locstats:tabulate=0,99 gives this: 0 14330/26% 14330/26% 1..99 17890/32% 32220/59% 100 22002/40% 54222/100%
Another variant would be to also tally what portion of available locations is mutable vs immutable (or distribution of the ratios, or whatever). That is, DW_AT_const_value is immutable. A location expression is immutable if it ends in DW_OP_implicit_value or DW_OP_stack_value. If an expression uses DW_OP_{,bit_}piece, then it can be partially mutable and partially immutable. You can probably just choose arbitrarily to count those on one side or the other, or perhaps tally them as a separate third statistic.
That's not yet there.
PM
This is now on the dwarf branch. Use like this: dwarflint --check=locstats binary
Neat!
I'm getting a lot of misses from the following areas:
- artificial variables/parameters (but some of them do have location)
The only ones I can think of to expect are "this" parameters in C++. Lots of these (pun inevitable) are probably the cases we expect to be getting DW_OP_GNU_implicit_pointer and were unrepresentable before.
I think this is exactly what Jakub was interested in seeing, i.e. to do a current gcc trunk bootstrap build, and see the stats for e.g. that build's libstdc++.so vs the build with the previous compiler.
- descendants of DIEs that have DW_AT_inlined == "inlined" or
"declared_inlined" (but many of them do have location)
In the abstract instance trees, it's normal to have all the parameters and variables without a location. I would expect only the statics to have a location in an abstract instance tree. What's really interesting is to how many of the corresponding DIEs in the concrete instance tree do have location or const_value.
- descendants of DIEs that are DW_TAG_inlined_subroutine (but there's so
much of them that that's no wonder. Ignoring those cuts off about 90% of variable/parameter DIEs)
These are the interesting ones, the concrete instance trees. When one (or more) of these has an abstract_origin pointing to a given abstract instance tree DIE, then it's only really interesting what proportion of the concrete instances have location/const_value. For a variable/formal_parameter in the abstract instance that has its own location/const_value, the corresponding concrete instance entry might just be omitted (should be, probably, by the spec).
I used c++/dwarf header for getting the scope. Does that/will that take into account the DW_AT_start_scope adjustments, or is that something that the client should take care of themselves?
I'm not sure which interfaces C++ you are referring to exactly, but there is no libdw code that makes use of DW_AT_start_scope implicitly. This certainly seems like something it could be worthwhile to have library help for.
Currently it emits a table like this:
That looks pretty fancy! I'll leave it to Jakub to say what the most useful kinds of reports are.
Great work!
Thanks, Roland
On Fri, Sep 17, 2010 at 06:32:29PM +0200, Petr Machata wrote:
21.07.2010 03:58, Roland McGrath wrote:
Jakub was interested in sampling some DWARF data to compare what one compiler vs another is doing with some broad statistics of semantic interest. The first particular thing to measure is how much location/value information we are getting for variables and parameters.
This is now on the dwarf branch. Use like this: dwarflint --check=locstats binary
Thanks.
Unfortunately it doesn't work too well with trunk gcc because of implicit_pointer. I've tried to patch
--- dwarflint/check_debug_loc_range.cc.jj 2010-09-20 14:01:41.000000000 +0200 +++ dwarflint/check_debug_loc_range.cc 2010-09-20 15:04:10.543533890 +0200 @@ -731,7 +731,8 @@ namespace { /* Operands are passed back as attribute forms. In particular, DW_FORM_dataX for X-byte operands, DW_FORM_[us]data for - ULEB128/SLEB128 operands, and DW_FORM_addr for 32b/64b operands. + ULEB128/SLEB128 operands, and DW_FORM_addr/DW_FORM_ref_addr + for 32b/64b operands. If the opcode takes no operands, 0 is passed.
Return value is false if we couldn't determine (i.e. invalid @@ -767,16 +768,21 @@ namespace form. For block forms, the value passed back in VALUEP is block length. */ bool - read_ctx_read_form (struct read_ctx *ctx, int address_size, uint8_t form, - uint64_t *valuep, struct where *where, const char *what, - bool *is_blockp) + read_ctx_read_form (struct read_ctx *ctx, struct cu *cu, + uint8_t form, uint64_t *valuep, struct where *where, + const char *what, bool *is_blockp) { if (is_blockp != NULL) *is_blockp = false; switch (form) { case DW_FORM_addr: - return read_ctx_read_offset (ctx, address_size == 8, valuep); + return read_ctx_read_offset (ctx, cu->head->address_size == 8, valuep); + case DW_FORM_ref_addr: + return read_ctx_read_offset (ctx, (cu->head->version >= 3 + ? cu->head->offset_size + : cu->head->address_size) == 8, + valuep); case DW_FORM_udata: return checked_read_uleb128 (ctx, valuep, where, what); case DW_FORM_sdata: @@ -831,7 +837,7 @@ namespace }
*is_blockp = true; - return read_ctx_read_form (ctx, address_size, dform, + return read_ctx_read_form (ctx, cu, dform, valuep, where, what, NULL) && read_ctx_skip (ctx, *valuep); } @@ -879,7 +885,7 @@ namespace bool isblock; uint64_t off = read_ctx_get_offset (ctx) + init_off;
- if (!read_ctx_read_form (ctx, cu->head->address_size, form, + if (!read_ctx_read_form (ctx, cu, form, valuep, where, str, &isblock)) { wr_error (*where) --- src/dwarf-opcodes.h.jj 2010-09-20 14:01:41.000000000 +0200 +++ src/dwarf-opcodes.h 2010-09-20 14:58:30.987377620 +0200 @@ -196,7 +196,7 @@ DW_OP_0 (DW_OP_push_object_address) \ DW_OP_1 (DW_OP_call2, DW_FORM_data2) \ DW_OP_1 (DW_OP_call4, DW_FORM_data4) \ - DW_OP_1 (DW_OP_call_ref, DW_FORM_addr) \ + DW_OP_1 (DW_OP_call_ref, DW_FORM_ref_addr) \ DW_OP_0 (DW_OP_form_tls_address) \ DW_OP_0 (DW_OP_GNU_push_tls_address) \ DW_OP_0 (DW_OP_call_frame_cfa) \ @@ -204,4 +204,6 @@ DW_OP_0 (DW_OP_GNU_uninit) \ /* DWARF 4 */ \ DW_OP_0 (DW_OP_stack_value) \ - DW_OP_1 (DW_OP_implicit_value, DW_FORM_block) + DW_OP_1 (DW_OP_implicit_value, DW_FORM_block) \ + /* GNU extensions */ \ + DW_OP_1 (DW_OP_GNU_implicit_pointer, DW_FORM_ref_addr)
but that isn't enough, with that it still complains: error: location expression: offset 0x1b496: can't decode opcode "unknown opcode 0". ... Didn't have time to debug this more...
Jakub
On Mon, Sep 20, 2010 at 03:06:39PM +0200, Jakub Jelinek wrote:
Unfortunately it doesn't work too well with trunk gcc because of implicit_pointer. I've tried to patch
My fault, telling it that DW_OP_GNU_implicit_pointer has just one operand obviously isn't a good idea. This one of course works better.
OT, is there a way to request numbers of 0.0% and 100% coverage separately (so that 0.1% and 99.99% coverages get attributed to other lines)?
--- dwarflint/check_debug_loc_range.cc.jj 2010-09-20 14:01:41.000000000 +0200 +++ dwarflint/check_debug_loc_range.cc 2010-09-20 15:04:10.543533890 +0200 @@ -731,7 +731,8 @@ namespace { /* Operands are passed back as attribute forms. In particular, DW_FORM_dataX for X-byte operands, DW_FORM_[us]data for - ULEB128/SLEB128 operands, and DW_FORM_addr for 32b/64b operands. + ULEB128/SLEB128 operands, and DW_FORM_addr/DW_FORM_ref_addr + for 32b/64b operands. If the opcode takes no operands, 0 is passed.
Return value is false if we couldn't determine (i.e. invalid @@ -767,16 +768,21 @@ namespace form. For block forms, the value passed back in VALUEP is block length. */ bool - read_ctx_read_form (struct read_ctx *ctx, int address_size, uint8_t form, - uint64_t *valuep, struct where *where, const char *what, - bool *is_blockp) + read_ctx_read_form (struct read_ctx *ctx, struct cu *cu, + uint8_t form, uint64_t *valuep, struct where *where, + const char *what, bool *is_blockp) { if (is_blockp != NULL) *is_blockp = false; switch (form) { case DW_FORM_addr: - return read_ctx_read_offset (ctx, address_size == 8, valuep); + return read_ctx_read_offset (ctx, cu->head->address_size == 8, valuep); + case DW_FORM_ref_addr: + return read_ctx_read_offset (ctx, (cu->head->version >= 3 + ? cu->head->offset_size + : cu->head->address_size) == 8, + valuep); case DW_FORM_udata: return checked_read_uleb128 (ctx, valuep, where, what); case DW_FORM_sdata: @@ -831,7 +837,7 @@ namespace }
*is_blockp = true; - return read_ctx_read_form (ctx, address_size, dform, + return read_ctx_read_form (ctx, cu, dform, valuep, where, what, NULL) && read_ctx_skip (ctx, *valuep); } @@ -879,7 +885,7 @@ namespace bool isblock; uint64_t off = read_ctx_get_offset (ctx) + init_off;
- if (!read_ctx_read_form (ctx, cu->head->address_size, form, + if (!read_ctx_read_form (ctx, cu, form, valuep, where, str, &isblock)) { wr_error (*where) --- src/dwarf-opcodes.h.jj 2010-09-20 14:01:41.000000000 +0200 +++ src/dwarf-opcodes.h 2010-09-20 17:02:57.448614397 +0200 @@ -196,7 +196,7 @@ DW_OP_0 (DW_OP_push_object_address) \ DW_OP_1 (DW_OP_call2, DW_FORM_data2) \ DW_OP_1 (DW_OP_call4, DW_FORM_data4) \ - DW_OP_1 (DW_OP_call_ref, DW_FORM_addr) \ + DW_OP_1 (DW_OP_call_ref, DW_FORM_ref_addr) \ DW_OP_0 (DW_OP_form_tls_address) \ DW_OP_0 (DW_OP_GNU_push_tls_address) \ DW_OP_0 (DW_OP_call_frame_cfa) \ @@ -204,4 +204,6 @@ DW_OP_0 (DW_OP_GNU_uninit) \ /* DWARF 4 */ \ DW_OP_0 (DW_OP_stack_value) \ - DW_OP_1 (DW_OP_implicit_value, DW_FORM_block) + DW_OP_1 (DW_OP_implicit_value, DW_FORM_block) \ + /* GNU extensions */ \ + DW_OP_2 (DW_OP_GNU_implicit_pointer, DW_FORM_ref_addr, DW_FORM_sdata)
Jakub
20.09.2010 18:31, Jakub Jelinek wrote:
On Mon, Sep 20, 2010 at 03:06:39PM +0200, Jakub Jelinek wrote:
Unfortunately it doesn't work too well with trunk gcc because of implicit_pointer. I've tried to patch
My fault, telling it that DW_OP_GNU_implicit_pointer has just one operand obviously isn't a good idea. This one of course works better.
Thanks.
OT, is there a way to request numbers of 0.0% and 100% coverage separately (so that 0.1% and 99.99% coverages get attributed to other lines)?
Yes for 100%, not for 0% (it uses normal integer division). I added the possibility of requesting sharp 0% coverage, you can now use "0.0" as a tabulation stop. --locstats:tabulate=0.0:10,99 now makes separate bucket for 0.0, then 0..10 etc and another separate bucket for 100%. It's on dwarf branch.
PM
Unfortunately it doesn't work too well with trunk gcc because of implicit_pointer.
Ah, yes. libdw does support reading it, but different parts of dwarflint do or don't inherit their format support from generic libdw code. I believe Petr also has yet to add the support for the new DWARF 4 encodings.
Thanks, Roland
17.09.2010 18:32, Petr Machata wrote:
Another variant would be to also tally what portion of available locations is mutable vs immutable (or distribution of the ratios, or whatever). That is, DW_AT_const_value is immutable. A location expression is immutable if it ends in DW_OP_implicit_value or DW_OP_stack_value. If an expression uses DW_OP_{,bit_}piece, then it can be partially mutable and partially immutable. You can probably just choose arbitrarily to count those on one side or the other, or perhaps tally them as a separate third statistic.
That's not yet there.
I didn't write about it, but it's been there for some time now. It's now possible to ignore DIEs that are "mutable" or "immutable". Mutability is determined depending on whether the expression contains DW_OP_{implicit|stack}_value before DW_OP_{,bit}_piece. If it does, it's mutable, otherwise it's immutable.
Since there can be multiple pieces per expression, one DIE may end up being classified as both mutable and immutable. There's no flag for "ignore not immutable". These stats can be computed in a roundabout way by first running dwarflint with no ignore, then with ignore immutable and subtracting. If that's something that is useful, I can teach the appropriate class to be able to ignore !mutable, or something like that.
And now it occurs to me that it needs to handle DW_OP_call to determine the mutability accurately, which is not currently done.
PM
I didn't write about it, but it's been there for some time now. It's now possible to ignore DIEs that are "mutable" or "immutable". Mutability is determined depending on whether the expression contains DW_OP_{implicit|stack}_value before DW_OP_{,bit}_piece. If it does, it's mutable, otherwise it's immutable.
Cool!
Since there can be multiple pieces per expression, one DIE may end up being classified as both mutable and immutable. There's no flag for "ignore not immutable". These stats can be computed in a roundabout way by first running dwarflint with no ignore, then with ignore immutable and subtracting. If that's something that is useful, I can teach the appropriate class to be able to ignore !mutable, or something like that.
Since it was Jakub who asked for this, he'll have to say what choices are most useful for the statistics he wants to see.
And now it occurs to me that it needs to handle DW_OP_call to determine the mutability accurately, which is not currently done.
Nothing we have generates DW_OP_call* so far, so that is not a big deal. (It could sit on the to-do list for many months and we won't mind.) But for completeness, yes, following DW_OP_call* as if the referent were spliced in place of the call is what you should do for all the checks.
Thanks, Roland
elfutils-devel@lists.fedorahosted.org