Using dwarf_getlocation it is possible to get single location descriptions and with dwarf_getlocation_addr it is possible to get a location list covering a specific address. But sometimes it is more convenient to get all ranges covered by a location list. For example when a specific address isn't covered and you want to find alternative addresses where a location description is defined.
dwarf_getlocations is modelled after dwarf_ranges. It enumerates the location ranges and descriptions covered by the given attribute. In the first call OFFSET should be zero and *BASEP need not be initialized. Returns -1 for errors, zero when there are no more locations to report, or a nonzero OFFSET value to pass to the next call. Each subsequent call must preserve *BASEP from the prior call. Successful calls fill in *STARTP and *ENDP with a contiguous address range and *EXPR with a pointer to an array of operations with length *EXPRLEN. If the attribute describes a single location description and not a location list the first call (with OFFSET zero) will return the location description in *EXPR with *STARTP set to zero and *ENDP set to minus one.
ptrdiff_t dwarf_getlocations (Dwarf_Attribute *attr, ptrdiff_t offset, Dwarf_Addr *basep, Dwarf_Addr *startp, Dwarf_Addr *endp, Dwarf_Op **expr, size_t *exprlen);
Signed-off-by: Mark Wielaard mjw@redhat.com --- libdw/ChangeLog | 9 ++ libdw/dwarf_getlocation.c | 182 ++++++++++++++++++++++++++++++++++++++------- libdw/libdw.h | 18 ++++- libdw/libdw.map | 5 + 4 files changed, 186 insertions(+), 28 deletions(-)
diff --git a/libdw/ChangeLog b/libdw/ChangeLog index aa1e2cc..18ddd9e 100644 --- a/libdw/ChangeLog +++ b/libdw/ChangeLog @@ -1,3 +1,12 @@ +2013-08-23 Mark Wielaard mjw@redhat.com + + * dwarf_getlocation.c (attr_ok): Also accept DW_AT_segment. + (attr_base_address): New function, taken from... + (dwarf_getlocation_addr): here. Use attr_base_address. + (dwarf_getlocations): New function. + * libdw.h (dwarf_getlocations): New function definition. + * libdw.map (ELFUTILS_0.157): New. + 2013-07-02 Mark Wielaard mjw@redhat.com
* dwarf_getsrclines.c (dwarf_getsrclines): Add new stack allocation diff --git a/libdw/dwarf_getlocation.c b/libdw/dwarf_getlocation.c index c3f3f47..2af0df7 100644 --- a/libdw/dwarf_getlocation.c +++ b/libdw/dwarf_getlocation.c @@ -1,5 +1,5 @@ /* Return location expression list. - Copyright (C) 2000-2010 Red Hat, Inc. + Copyright (C) 2000-2010, 2013 Red Hat, Inc. This file is part of elfutils. Written by Ulrich Drepper drepper@redhat.com, 2000.
@@ -56,6 +56,7 @@ attr_ok (Dwarf_Attribute *attr) case DW_AT_frame_base: case DW_AT_return_addr: case DW_AT_static_link: + case DW_AT_segment: break;
default: @@ -567,6 +568,37 @@ dwarf_getlocation (attr, llbuf, listlen) return getlocation (attr->cu, &block, llbuf, listlen, cu_sec_idx (attr->cu)); }
+static int +attr_base_address (attr, basep) + Dwarf_Attribute *attr; + Dwarf_Addr *basep; +{ + /* Fetch the CU's base address. */ + Dwarf_Die cudie = CUDIE (attr->cu); + + /* Find the base address of the compilation unit. It will + normally be specified by DW_AT_low_pc. In DWARF-3 draft 4, + the base address could be overridden by DW_AT_entry_pc. It's + been removed, but GCC emits DW_AT_entry_pc and not DW_AT_lowpc + for compilation units with discontinuous ranges. */ + Dwarf_Attribute attr_mem; + if (unlikely (INTUSE(dwarf_lowpc) (&cudie, basep) != 0) + && INTUSE(dwarf_formaddr) (INTUSE(dwarf_attr) (&cudie, + DW_AT_entry_pc, + &attr_mem), + basep) != 0) + { + if (INTUSE(dwarf_errno) () != 0) + return -1; + + /* The compiler provided no base address when it should + have. Buggy GCC does this when it used absolute + addresses in the location list and no DW_AT_ranges. */ + *basep = 0; + } + return 0; +} + int dwarf_getlocation_addr (attr, address, llbufs, listlens, maxlocs) Dwarf_Attribute *attr; @@ -646,32 +678,8 @@ dwarf_getlocation_addr (attr, address, llbufs, listlens, maxlocs) goto invalid; readp += block.length;
- if (base == (Dwarf_Addr) -1) - { - /* Fetch the CU's base address. */ - Dwarf_Die cudie = CUDIE (attr->cu); - - /* Find the base address of the compilation unit. It will - normally be specified by DW_AT_low_pc. In DWARF-3 draft 4, - the base address could be overridden by DW_AT_entry_pc. It's - been removed, but GCC emits DW_AT_entry_pc and not DW_AT_lowpc - for compilation units with discontinuous ranges. */ - Dwarf_Attribute attr_mem; - if (unlikely (INTUSE(dwarf_lowpc) (&cudie, &base) != 0) - && INTUSE(dwarf_formaddr) (INTUSE(dwarf_attr) (&cudie, - DW_AT_entry_pc, - &attr_mem), - &base) != 0) - { - if (INTUSE(dwarf_errno) () != 0) - return -1; - - /* The compiler provided no base address when it should - have. Buggy GCC does this when it used absolute - addresses in the location list and no DW_AT_ranges. */ - base = 0; - } - } + if (base == (Dwarf_Addr) -1 && attr_base_address (attr, &base) != 0) + return -1;
if (address >= base + begin && address < base + end) { @@ -687,3 +695,123 @@ dwarf_getlocation_addr (attr, address, llbufs, listlens, maxlocs)
return got; } + +ptrdiff_t +dwarf_getlocations (attr, offset, basep, startp, endp, expr, exprlen) + Dwarf_Attribute *attr; + ptrdiff_t offset; + Dwarf_Addr *basep; + Dwarf_Addr *startp; + Dwarf_Addr *endp; + Dwarf_Op **expr; + size_t *exprlen; +{ + if (! attr_ok (attr)) + return -1; + + /* 1 is an invalid offset, meaning no more locations. */ + if (offset == 1) + return 0; + + if (offset == 0) + { + /* If it has a block form, it's a single location expression. */ + Dwarf_Block block; + if (INTUSE(dwarf_formblock) (attr, &block) == 0) + { + if (getlocation (attr->cu, &block, expr, exprlen, + cu_sec_idx (attr->cu)) != 0) + return -1; + + /* This is the one and only location covering everything. */ + *startp = 0; + *endp = -1; + return 1; + } + + int error = INTUSE(dwarf_errno) (); + if (unlikely (error != DWARF_E_NO_BLOCK)) + { + __libdw_seterrno (error); + return -1; + } + + int result = check_constant_offset (attr, expr, exprlen); + if (result != 1) + { + if (result == 0) + { + /* This is the one and only location covering everything. */ + *startp = 0; + *endp = -1; + return 1; + } + return result; + } + + /* We must be looking at a true loclistptr, fetch the initial + base address and offset. */ + if (attr_base_address (attr, basep) != 0) + return -1; + + Dwarf_Word start_offset; + if (__libdw_formptr (attr, IDX_debug_loc, + DWARF_E_NO_LOCLIST, + NULL, &start_offset) == NULL) + return -1; + offset = start_offset; + } + + const Elf_Data *d = attr->cu->dbg->sectiondata[IDX_debug_loc]; + if (d == NULL) + { + __libdw_seterrno (DWARF_E_NO_LOCLIST); + return -1; + } + + unsigned char *readp = d->d_buf + offset; + unsigned char *readendp = d->d_buf + d->d_size; + + next: + if (readendp - readp < attr->cu->address_size * 2) + { + invalid: + __libdw_seterrno (DWARF_E_INVALID_DWARF); + return -1; + } + + Dwarf_Addr begin; + Dwarf_Addr end; + + switch (__libdw_read_begin_end_pair_inc (attr->cu->dbg, IDX_debug_loc, + &readp, attr->cu->address_size, + &begin, &end, basep)) + { + case 0: /* got location range. */ + break; + case 1: /* base address setup. */ + goto next; + case 2: /* end of loclist */ + return 0; + default: /* error */ + return -1; + } + + if (readendp - readp < 2) + goto invalid; + + /* We have a location expression. */ + Dwarf_Block block; + block.length = read_2ubyte_unaligned_inc (attr->cu->dbg, readp); + block.data = readp; + if (readendp - readp < (ptrdiff_t) block.length) + goto invalid; + readp += block.length; + + if (getlocation (attr->cu, &block, expr, exprlen, IDX_debug_loc) != 0) + return -1; + + *startp = *basep + begin; + *endp = *basep + end; + return readp - (unsigned char *) d->d_buf; +} diff --git a/libdw/libdw.h b/libdw/libdw.h index f5fc4e2..898aa74 100644 --- a/libdw/libdw.h +++ b/libdw/libdw.h @@ -1,5 +1,5 @@ /* Interfaces for libdw. - Copyright (C) 2002-2010 Red Hat, Inc. + Copyright (C) 2002-2010, 2013 Red Hat, Inc. This file is part of elfutils.
This file is free software; you can redistribute it and/or modify @@ -630,6 +630,22 @@ extern int dwarf_getlocation_addr (Dwarf_Attribute *attr, Dwarf_Addr address, Dwarf_Op **exprs, size_t *exprlens, size_t nlocs);
+/* Enumerate the locations ranges and descriptions covered by the + given attribute. In the first call OFFSET should be zero and + *BASEP need not be initialized. Returns -1 for errors, zero when + there are no more locations to report, or a nonzero OFFSET + value to pass to the next call. Each subsequent call must preserve + *BASEP from the prior call. Successful calls fill in *STARTP and + *ENDP with a contiguous address range and *EXPR with a pointer to + an array of operations with length *EXPRLEN. If the attribute + describes a single location description and not a location list the + first call (with OFFSET zero) will return the location description + in *EXPR with *STARTP set to zero and *ENDP set to minus one. */ +extern ptrdiff_t dwarf_getlocations (Dwarf_Attribute *attr, + ptrdiff_t offset, Dwarf_Addr *basep, + Dwarf_Addr *startp, Dwarf_Addr *endp, + Dwarf_Op **expr, size_t *exprlen); + /* Return the block associated with a DW_OP_implicit_value operation. The OP pointer must point into an expression that dwarf_getlocation or dwarf_getlocation_addr has returned given the same ATTR. */ diff --git a/libdw/libdw.map b/libdw/libdw.map index d38a8ef..2d2d37c 100644 --- a/libdw/libdw.map +++ b/libdw/libdw.map @@ -260,3 +260,8 @@ ELFUTILS_0.156 { # Replaced ELFUTILS_0.122 version, which has a wrapper without add_p_vaddr. dwfl_report_elf; } ELFUTILS_0.149; + +ELFUTILS_0.157 { + global: + dwarf_getlocations; +} ELFUTILS_0.156;
On Fri, 23 Aug 2013 16:12:37 +0200, Mark Wielaard wrote:
Using dwarf_getlocation it is possible to get single location descriptions and with dwarf_getlocation_addr it is possible to get a location list covering a specific address. But sometimes it is more convenient to get all ranges covered by a location list.
Just I find that with dwarf_getlocations in place one could rebase dwarf_getlocation on top of it. Although one would have to hack there prevention of getlocation() for non-matching ranges. And hope gcc inlines it all.
Otherwise it looks fine to me.
Thanks, Jan
On Mon, 2013-09-02 at 20:59 +0200, Jan Kratochvil wrote:
On Fri, 23 Aug 2013 16:12:37 +0200, Mark Wielaard wrote:
Using dwarf_getlocation it is possible to get single location descriptions and with dwarf_getlocation_addr it is possible to get a location list covering a specific address. But sometimes it is more convenient to get all ranges covered by a location list.
Just I find that with dwarf_getlocations in place one could rebase dwarf_getlocation on top of it.
Good point, assuming you meant dwarf_getlocation_addr, that removes some duplicate code. Revised patch attached.
Thanks,
Mark
On Tue, 03 Sep 2013 13:43:29 +0200, Mark Wielaard wrote:
Good point, assuming you meant dwarf_getlocation_addr, that removes some duplicate code. Revised patch attached.
You have not addressed my previous note # Although one would have to hack there prevention of getlocation() for # non-matching ranges.
Currently you call getlocation->__libdw_intern_expression for every range in the location list, even if that range does not contain our PC. That is both CPU expensive and it also "leaks" memory (until cu->dbg gets destroyed) even more than formerly (formerly only one range got "leaked" on each dwarf_getlocation call).
My idea was more to create dwarf_getlocations_internal without its last getlocation call, use that from refactored dwarf_getlocation and create current public dwarf_getlocations as a wrapper around dwarf_getlocations_internal with that getlocation call.
But then one may argue it is no longer such a simplification.
And maybe also you do not find it as an issue as location lists are never too long anyway.
Jan
On Wed, 2013-09-04 at 14:14 +0200, Jan Kratochvil wrote:
Currently you call getlocation->__libdw_intern_expression for every range in the location list, even if that range does not contain our PC. That is both CPU expensive and it also "leaks" memory (until cu->dbg gets destroyed) even more than formerly (formerly only one range got "leaked" on each dwarf_getlocation call).
My idea was more to create dwarf_getlocations_internal without its last getlocation call, use that from refactored dwarf_getlocation and create current public dwarf_getlocations as a wrapper around dwarf_getlocations_internal with that getlocation call.
Good point, I missed that. It is always good to do less work if at all possible. I implemented it slightly differently from your suggestion, but I believe it addresses your point. See new patch attached.
Thanks,
Mark
On Wed, 04 Sep 2013 16:30:19 +0200, Mark Wielaard wrote:
Good point, I missed that. It is always good to do less work if at all possible. I implemented it slightly differently from your suggestion, but I believe it addresses your point. See new patch attached.
This makes address == 0 a special case.
I do not have an overview how elfutils handles that but at least GDB tries to handle address 0 as general valid address as it is a normal code location on many embedded targets. (Although for example GNU/Linux-targeted code does not handle address 0 correctly as it is not much a problem for GNU/Linux targets.)
Jan
On Wed, 2013-09-04 at 16:42 +0200, Jan Kratochvil wrote:
On Wed, 04 Sep 2013 16:30:19 +0200, Mark Wielaard wrote:
Good point, I missed that. It is always good to do less work if at all possible. I implemented it slightly differently from your suggestion, but I believe it addresses your point. See new patch attached.
This makes address == 0 a special case.
I do not have an overview how elfutils handles that but at least GDB tries to handle address 0 as general valid address as it is a normal code location on many embedded targets. (Although for example GNU/Linux-targeted code does not handle address 0 correctly as it is not much a problem for GNU/Linux targets.)
We can use -1. Which should never match. I'll test the same patch with:
diff --git a/libdw/dwarf_getlocation.c b/libdw/dwarf_getlocation.c index cb4b483..c9df8f4 100644 --- a/libdw/dwarf_getlocation.c +++ b/libdw/dwarf_getlocation.c @@ -678,8 +678,8 @@ getlocations_addr (attr, offset, basep, startp, endp, address, *startp = *basep + begin; *endp = *basep + end;
- /* If address is zero we want them all, otherwise only those that match. */ - if (address != 0 && (address < *endp || address >= *startp)) + /* If address is minus one we want them all, otherwise only matching. */ + if (address != (Dwarf_Word) -1 && (address < *endp || address >= *startp)) goto next;
if (getlocation (attr->cu, &block, expr, exprlen, IDX_debug_loc) != 0) @@ -828,6 +828,6 @@ dwarf_getlocations (attr, offset, basep, startp, endp, expr, exprlen) return -1; }
- return getlocations_addr (attr, offset, basep, startp, endp, 0, d, - expr, exprlen); + return getlocations_addr (attr, offset, basep, startp, endp, + (Dwarf_Word) -1, d, expr, exprlen); }
Thanks,
Mark
On Wed, 04 Sep 2013 17:03:55 +0200, Mark Wielaard wrote:
We can use -1. Which should never match. I'll test the same patch with:
OK.
diff --git a/libdw/dwarf_getlocation.c b/libdw/dwarf_getlocation.c index cb4b483..c9df8f4 100644 --- a/libdw/dwarf_getlocation.c +++ b/libdw/dwarf_getlocation.c @@ -678,8 +678,8 @@ getlocations_addr (attr, offset, basep, startp, endp, address, *startp = *basep + begin; *endp = *basep + end;
- /* If address is zero we want them all, otherwise only those that match. */
- if (address != 0 && (address < *endp || address >= *startp))
- /* If address is minus one we want them all, otherwise only matching. */
- if (address != (Dwarf_Word) -1 && (address < *endp || address >= *startp))
I did not test it and I am curious it can work but I think here should be: if (address != (Dwarf_Word) -1 && (address < *startp || address >= *endp))
goto next;
Thanks, Jan
On Wed, 2013-09-04 at 17:42 +0200, Jan Kratochvil wrote:
On Wed, 04 Sep 2013 17:03:55 +0200, Mark Wielaard wrote:
- /* If address is zero we want them all, otherwise only those that match. */
- if (address != 0 && (address < *endp || address >= *startp))
- /* If address is minus one we want them all, otherwise only matching. */
- if (address != (Dwarf_Word) -1 && (address < *endp || address >= *startp))
I did not test it and I am curious it can work but I think here should be: if (address != (Dwarf_Word) -1 && (address < *startp || address >= *endp))
It couldn't work! :) I had seen the same and was indeed scratching my head why my testing didn't catch that. But in all tests when dwarf_location_addr was used, it was hitting an expression block, and not a loc list. So this part of the code was never used... duh.
So I extended the varlocs test to explicitly test all loc lists found:
diff --git a/tests/varlocs.c b/tests/varlocs.c index cda18b6..6f4d490 100644 --- a/tests/varlocs.c +++ b/tests/varlocs.c @@ -687,8 +687,23 @@ print_varlocs (Dwarf_Die *funcdie) printf (" (%" PRIx64 ",%" PRIx64 ") <empty range>\n", begin, end); // XXX report? else - print_expr_block_addrs (&attr, begin, end, - expr, exprlen); + { + print_expr_block_addrs (&attr, begin, end, + expr, exprlen); + + // Extra sanity check for dwarf_getlocation_addr + // Must at least find one range for begin and end-1. + Dwarf_Op *expraddr; + size_t expraddr_len; + int locs = dwarf_getlocation_addr (&attr, begin, + &expraddr, + &expraddr_len, 1); + assert (locs == 1); + locs = dwarf_getlocation_addr (&attr, end - 1, + &expraddr, + &expraddr_len, 1); + assert (locs == 1); + }
if (offset < 0) error (EXIT_FAILURE, 0, "dwarf_getlocations: %s",
That showed the bug, and another slightly more subtle one:
@@ -756,7 +756,8 @@ dwarf_getlocation_addr (attr, address, llbufs, listlens, maxlocs) ++got; }
- if (off != 0) + /* We might stop early, so off can be zero or positive on success. */ + if (off < 0) return -1;
return got;
I pushed those fixlets to the mjw/dwarf_location git branch, which contains these three patches combined (the first two are independent, but the test case is for both):
libdw: Add new function dwarf_getlocations. libdw: Add new functions dwarf_getlocation_attr and dwarf_getlocation_die. tests: Add new varlocs test for dwarf_getlocation* functions.
Cheers,
Mark
On Wed, 04 Sep 2013 22:04:01 +0200, Mark Wielaard wrote:
I pushed those fixlets to the mjw/dwarf_location git branch, which contains these three patches combined (the first two are independent, but the test case is for both):
libdw: Add new function dwarf_getlocations. libdw: Add new functions dwarf_getlocation_attr and dwarf_getlocation_die. tests: Add new varlocs test for dwarf_getlocation* functions.
BTW otherwise I already reviewed it so from my side it is OK.
Thanks, Jan
On Wed, 2013-09-04 at 22:13 +0200, Jan Kratochvil wrote:
On Wed, 04 Sep 2013 22:04:01 +0200, Mark Wielaard wrote:
I pushed those fixlets to the mjw/dwarf_location git branch, which contains these three patches combined (the first two are independent, but the test case is for both):
libdw: Add new function dwarf_getlocations. libdw: Add new functions dwarf_getlocation_attr and dwarf_getlocation_die. tests: Add new varlocs test for dwarf_getlocation* functions.
BTW otherwise I already reviewed it so from my side it is OK.
Thanks, I pushed those patches to master now.
elfutils-devel@lists.fedorahosted.org