Support for --debug-dump=loc in readelf.py and in the test (#304)

diff --git a/elftools/dwarf/descriptions.py b/elftools/dwarf/descriptions.py
index 29f818f..a7a7599 100644
--- a/elftools/dwarf/descriptions.py
+++ b/elftools/dwarf/descriptions.py
@@ -622,5 +622,7 @@
             return "%s: %d <0x%x>" % (opcode_name, args[0], args[1] + cu_offset)
         elif opcode_name == 'DW_OP_GNU_const_type':
             return "%s: <0x%x>  %d byte block: %s " % (opcode_name, args[0] + cu_offset, len(args[1]), ' '.join("%x" % b for b in args[1]))
+        elif opcode_name == 'DW_OP_GNU_regval_type':
+            return "%s: %d (%s) <0x%x>" % (opcode_name, args[0], describe_reg_name(args[0], _MACHINE_ARCH), args[1] + cu_offset)
         else:
             return '<unknown %s>' % opcode_name
diff --git a/elftools/dwarf/locationlists.py b/elftools/dwarf/locationlists.py
index 2f8c51e..a503e43 100644
--- a/elftools/dwarf/locationlists.py
+++ b/elftools/dwarf/locationlists.py
@@ -12,8 +12,8 @@
 from ..common.utils import struct_parse
 
 LocationExpr = namedtuple('LocationExpr', 'loc_expr')
-LocationEntry = namedtuple('LocationEntry', 'begin_offset end_offset loc_expr')
-BaseAddressEntry = namedtuple('BaseAddressEntry', 'base_address')
+LocationEntry = namedtuple('LocationEntry', 'entry_offset begin_offset end_offset loc_expr')
+BaseAddressEntry = namedtuple('BaseAddressEntry', 'entry_offset base_address')
 
 class LocationLists(object):
     """ A single location list is a Python list consisting of LocationEntry or
@@ -46,6 +46,7 @@
     def _parse_location_list_from_stream(self):
         lst = []
         while True:
+            entry_offset = self.stream.tell()
             begin_offset = struct_parse(
                 self.structs.Dwarf_target_addr(''), self.stream)
             end_offset = struct_parse(
@@ -55,7 +56,7 @@
                 break
             elif begin_offset == self._max_addr:
                 # Base address selection entry
-                lst.append(BaseAddressEntry(base_address=end_offset))
+                lst.append(BaseAddressEntry(entry_offset=entry_offset, base_address=end_offset))
             else:
                 # Location list entry
                 expr_len = struct_parse(
@@ -64,6 +65,7 @@
                                          self.stream)
                                 for i in range(expr_len)]
                 lst.append(LocationEntry(
+                    entry_offset=entry_offset,
                     begin_offset=begin_offset,
                     end_offset=end_offset,
                     loc_expr=loc_expr))
diff --git a/examples/reference_output/dwarf_location_info.out b/examples/reference_output/dwarf_location_info.out
index 9e1fe8e..01c8933 100644
--- a/examples/reference_output/dwarf_location_info.out
+++ b/examples/reference_output/dwarf_location_info.out
@@ -5,9 +5,9 @@
       (DW_OP_addr: 400608)
   Found a compile unit at offset 258, length 156
    DIE DW_TAG_subprogram. attr DW_AT_frame_base.
-      LocationEntry(begin_offset=0, end_offset=1, loc_expr=[119, 8]) <<(DW_OP_breg7 (rsp): 8)>>
-      LocationEntry(begin_offset=1, end_offset=4, loc_expr=[119, 16]) <<(DW_OP_breg7 (rsp): 16)>>
-      LocationEntry(begin_offset=4, end_offset=43, loc_expr=[118, 16]) <<(DW_OP_breg6 (rbp): 16)>>
+      LocationEntry(entry_offset=0, begin_offset=0, end_offset=1, loc_expr=[119, 8]) <<(DW_OP_breg7 (rsp): 8)>>
+      LocationEntry(entry_offset=20, begin_offset=1, end_offset=4, loc_expr=[119, 16]) <<(DW_OP_breg7 (rsp): 16)>>
+      LocationEntry(entry_offset=40, begin_offset=4, end_offset=43, loc_expr=[118, 16]) <<(DW_OP_breg6 (rbp): 16)>>
    DIE DW_TAG_formal_parameter. attr DW_AT_location.
       (DW_OP_fbreg: -20)
    DIE DW_TAG_formal_parameter. attr DW_AT_location.
@@ -18,16 +18,16 @@
    DIE DW_TAG_subprogram. attr DW_AT_frame_base.
       (DW_OP_breg7 (rsp): 8)
    DIE DW_TAG_subprogram. attr DW_AT_frame_base.
-      LocationEntry(begin_offset=16, end_offset=64, loc_expr=[119, 8]) <<(DW_OP_breg7 (rsp): 8)>>
-      LocationEntry(begin_offset=64, end_offset=153, loc_expr=[119, 192, 0]) <<(DW_OP_breg7 (rsp): 64)>>
+      LocationEntry(entry_offset=76, begin_offset=16, end_offset=64, loc_expr=[119, 8]) <<(DW_OP_breg7 (rsp): 8)>>
+      LocationEntry(entry_offset=96, begin_offset=64, end_offset=153, loc_expr=[119, 192, 0]) <<(DW_OP_breg7 (rsp): 64)>>
    DIE DW_TAG_formal_parameter. attr DW_AT_location.
-      LocationEntry(begin_offset=16, end_offset=85, loc_expr=[85]) <<(DW_OP_reg5 (rdi))>>
-      LocationEntry(begin_offset=85, end_offset=143, loc_expr=[94]) <<(DW_OP_reg14 (r14))>>
+      LocationEntry(entry_offset=133, begin_offset=16, end_offset=85, loc_expr=[85]) <<(DW_OP_reg5 (rdi))>>
+      LocationEntry(entry_offset=152, begin_offset=85, end_offset=143, loc_expr=[94]) <<(DW_OP_reg14 (r14))>>
    DIE DW_TAG_formal_parameter. attr DW_AT_location.
-      LocationEntry(begin_offset=16, end_offset=85, loc_expr=[84]) <<(DW_OP_reg4 (rsi))>>
-      LocationEntry(begin_offset=85, end_offset=138, loc_expr=[93]) <<(DW_OP_reg13 (r13))>>
+      LocationEntry(entry_offset=187, begin_offset=16, end_offset=85, loc_expr=[84]) <<(DW_OP_reg4 (rsi))>>
+      LocationEntry(entry_offset=206, begin_offset=85, end_offset=138, loc_expr=[93]) <<(DW_OP_reg13 (r13))>>
    DIE DW_TAG_formal_parameter. attr DW_AT_location.
-      LocationEntry(begin_offset=16, end_offset=85, loc_expr=[81]) <<(DW_OP_reg1 (rdx))>>
-      LocationEntry(begin_offset=85, end_offset=133, loc_expr=[92]) <<(DW_OP_reg12 (r12))>>
+      LocationEntry(entry_offset=241, begin_offset=16, end_offset=85, loc_expr=[81]) <<(DW_OP_reg1 (rdx))>>
+      LocationEntry(entry_offset=260, begin_offset=85, end_offset=133, loc_expr=[92]) <<(DW_OP_reg12 (r12))>>
    DIE DW_TAG_variable. attr DW_AT_location.
-      LocationEntry(begin_offset=92, end_offset=123, loc_expr=[83]) <<(DW_OP_reg3 (rbx))>>
+      LocationEntry(entry_offset=295, begin_offset=92, end_offset=123, loc_expr=[83]) <<(DW_OP_reg3 (rbx))>>
diff --git a/scripts/readelf.py b/scripts/readelf.py
index 0c91228..f9750e7 100755
--- a/scripts/readelf.py
+++ b/scripts/readelf.py
@@ -55,10 +55,11 @@
 from elftools.dwarf.descriptions import (
     describe_reg_name, describe_attr_value, set_global_machine_arch,
     describe_CFI_instructions, describe_CFI_register_rule,
-    describe_CFI_CFA_rule,
+    describe_CFI_CFA_rule, describe_DWARF_expr
     )
 from elftools.dwarf.constants import (
     DW_LNS_copy, DW_LNS_set_file, DW_LNE_define_file)
+from elftools.dwarf.locationlists import LocationParser, LocationEntry    
 from elftools.dwarf.callframe import CIE, FDE, ZERO
 
 
@@ -788,6 +789,8 @@
             self._dump_debug_aranges()
         elif dump_what in { 'pubtypes', 'pubnames' }:
             self._dump_debug_namelut(dump_what)
+        elif dump_what == 'loc':
+            self._dump_debug_locations()
         else:
             self._emitline('debug dump not yet supported for "%s"' % dump_what)
 
@@ -1332,6 +1335,70 @@
                     self._dwarfinfo.debug_frame_sec,
                     self._dwarfinfo.CFI_entries())
 
+    def _dump_debug_locations(self):
+        """ Dump the location lists from .debug_location section
+        """
+        def _get_cu_base(cu):
+            top_die = cu.get_top_DIE()
+            attr = top_die.attributes
+            if 'DW_AT_low_pc' in attr:
+                return attr['DW_AT_low_pc'].value
+            elif 'DW_AT_entry_pc' in attr:
+                return attr['DW_AT_entry_pc'].value
+            else:
+                raise ValueError("Can't find the base IP (low_pc) for a CU")                    
+
+        di = self._dwarfinfo
+        loc_lists = di.location_lists()
+        if not loc_lists: # No locations section - readelf outputs nothing
+            return
+
+        loc_lists = list(loc_lists.iter_location_lists())
+        if len(loc_lists) == 0:
+            # Present but empty locations section - readelf outputs a message
+            self._emitline("\nSection '%s' has no debugging data." % di.debug_loc_sec.name)
+            return
+
+        # To dump a location list, one needs to know the CU.
+        # Scroll through DIEs once, list the known location list offsets
+        cu_map = dict() # Loc list offset => CU            
+        for cu in di.iter_CUs():
+            for die in cu.iter_DIEs():
+                for key in die.attributes:
+                    attr = die.attributes[key]
+                    if (LocationParser.attribute_has_location(attr, cu['version']) and
+                        not LocationParser._attribute_has_loc_expr(attr, cu['version'])):
+                        cu_map[attr.value] = cu
+
+        addr_size = di.config.default_address_size # In bytes, 4 or 8
+        addr_width = addr_size * 2 # In hex digits, 8 or 16
+        line_template = "    %%08x %%0%dx %%0%dx %%s%%s" % (addr_width, addr_width)                
+
+        self._emitline('Contents of the %s section:\n' % di.debug_loc_sec.name)
+        self._emitline('    Offset   Begin            End              Expression')
+        for loc_list in loc_lists:
+            cu = cu_map.get(loc_list[0].entry_offset, False)
+            if not cu:
+                raise ValueError("Location list can't be tracked to a CU")
+            base_ip = _get_cu_base(cu)
+            for entry in loc_list:
+                # TODO: support BaseAddressEntry lines
+                expr = describe_DWARF_expr(entry.loc_expr, cu.structs, cu.cu_offset)
+                postfix = ' (start == end)' if entry.begin_offset == entry.end_offset else ''
+                self._emitline(line_template % (
+                    entry.entry_offset,
+                    base_ip + entry.begin_offset,
+                    base_ip + entry.end_offset,
+                    expr,
+                    postfix))
+            # Pyelftools doesn't store the terminating entry,
+            # but readelf emits its offset, so this should too.
+            last = loc_list[-1]
+            last_len = 2*addr_size
+            if isinstance(last, LocationEntry):
+                last_len += 2 + len(last.loc_expr)
+            self._emitline("    %08x <End of list>" % (last.entry_offset + last_len))
+
     def _display_arch_specific_arm(self):
         """ Display the ARM architecture-specific info contained in the file.
         """
@@ -1419,7 +1486,7 @@
             action='store', dest='debug_dump_what', metavar='<what>',
             help=(
                 'Display the contents of DWARF debug sections. <what> can ' +
-                'one of {info,decodedline,frames,frames-interp,aranges,pubtypes,pubnames}'))
+                'one of {info,decodedline,frames,frames-interp,aranges,pubtypes,pubnames,loc}'))
     argparser.add_argument('--traceback',
                            action='store_true', dest='show_traceback',
                            help='Dump the Python traceback on ELFError'
diff --git a/test/run_readelf_tests.py b/test/run_readelf_tests.py
index f4d06ae..95cfbd2 100755
--- a/test/run_readelf_tests.py
+++ b/test/run_readelf_tests.py
@@ -63,7 +63,7 @@
             '--debug-dump=info', '--debug-dump=decodedline',
             '--debug-dump=frames', '--debug-dump=frames-interp',
             '--debug-dump=aranges', '--debug-dump=pubtypes',
-            '--debug-dump=pubnames'
+            '--debug-dump=pubnames', '--debug-dump=loc'
             ]
     else:
         options = [opt]
diff --git a/test/testfiles_for_readelf/dwarf_gnuops2.o.elf b/test/testfiles_for_readelf/dwarf_gnuops2.o.elf
deleted file mode 100644
index 0d3bb20..0000000
--- a/test/testfiles_for_readelf/dwarf_gnuops2.o.elf
+++ /dev/null
Binary files differ
diff --git a/test/testfiles_for_readelf/dwarf_gnuops3.o.elf b/test/testfiles_for_readelf/dwarf_gnuops3.o.elf
deleted file mode 100644
index 9ae256d..0000000
--- a/test/testfiles_for_readelf/dwarf_gnuops3.o.elf
+++ /dev/null
Binary files differ