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(a)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(a)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(a)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(a)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;
--
1.7.1