Parse PT_NOTE segment and expose it with the new '-n' option to readelf.
authorAlex Deymo <deymo@chromium.org>
Sun, 17 Aug 2014 20:15:46 +0000 (13:15 -0700)
committerAlex Deymo <deymo@chromium.org>
Thu, 21 Aug 2014 16:46:53 +0000 (09:46 -0700)
The PT_NOTE segment includes a list of notes with a header, name and
description. GNU ld includes in this segment the GNU build-id and GNU
ABI version information.

The '-n' or '--notes' option to readelf displays the notes contained
in the ELF file.

elftools/common/utils.py
elftools/elf/descriptions.py
elftools/elf/elffile.py
elftools/elf/enums.py
elftools/elf/segments.py
elftools/elf/structs.py
scripts/readelf.py
test/run_readelf_tests.py

index a2c9edbe356c465fbaaed47d0a4f77b0747ee256..d8acefab2c922eea688ecfca25dc786076e0f973 100644 (file)
@@ -88,9 +88,14 @@ def preserve_stream_pos(stream):
     stream.seek(saved_pos)
 
 
+def roundup(num, bits):
+    """ Round up a number to nearest multiple of 2^bits. The result is a number
+        where the least significant bits passed in bits are 0.
+    """
+    return (num - 1 | (1 << bits) - 1) + 1
+
 #------------------------- PRIVATE -------------------------
 
 def _assert_with_exception(cond, msg, exception_type):
     if not cond:
         raise exception_type(msg)
-
index 6d108c2a3401475b72d2a9d48a74d9c9fa1f8e39..a25e51b81dc9d8c8ef76e045c35694a15a83664d 100644 (file)
@@ -110,6 +110,22 @@ def describe_ver_flags(x):
         VER_FLAGS.VER_FLG_BASE,
         VER_FLAGS.VER_FLG_INFO) if x & flag)
 
+def describe_note(x):
+  n_desc = x['n_desc']
+  desc = ''
+  if x['n_type'] == 'NT_GNU_ABI_TAG':
+      desc = '\n    OS: %s, ABI: %d.%d.%d' % (
+          _DESCR_NOTE_ABI_TAG_OS.get(n_desc['abi_os'], _unknown),
+          n_desc['abi_major'], n_desc['abi_minor'], n_desc['abi_tiny'])
+  elif x['n_type'] == 'NT_GNU_BUILD_ID':
+      desc = '\n    Build ID: %s' % (n_desc)
+
+  note_type = (x['n_type'] if isinstance(x['n_type'], str)
+               else 'Unknown note type:')
+  note_type_desc = ('0x%.8x' % x['n_type'] if isinstance(x['n_type'], int) else
+                    _DESCR_NOTE_N_TYPE.get(x['n_type'], _unknown))
+  return '%s (%s)%s' % (note_type, note_type_desc, desc)
+
 #-------------------------------------------------------------------------------
 _unknown = '<unknown>'
 
@@ -304,6 +320,24 @@ _DESCR_VER_FLAGS = {
     VER_FLAGS.VER_FLG_INFO: 'INFO',
 }
 
+# PT_NOTE section types
+_DESCR_NOTE_N_TYPE = dict(
+    NT_GNU_ABI_TAG='ABI version tag',
+    NT_GNU_HWCAP='DSO-supplied software HWCAP info',
+    NT_GNU_BUILD_ID='unique build ID bitstring',
+    NT_GNU_GOLD_VERSION='gold version',
+)
+
+# Values in GNU .note.ABI-tag notes (n_type=='NT_GNU_ABI_TAG')
+_DESCR_NOTE_ABI_TAG_OS = dict(
+    ELF_NOTE_OS_LINUX='Linux',
+    ELF_NOTE_OS_GNU='GNU',
+    ELF_NOTE_OS_SOLARIS2='Solaris 2',
+    ELF_NOTE_OS_FREEBSD='FreeBSD',
+    ELF_NOTE_OS_NETBSD='NetBSD',
+    ELF_NOTE_OS_SYLLABLE='Syllable',
+)
+
 _DESCR_RELOC_TYPE_i386 = dict(
         (v, k) for k, v in iteritems(ENUM_RELOC_TYPE_i386))
 
index 15aa07e7f95bfac4314332162b7eb14550ad591a..4132c1ed109fa3b03d17872f877e2e99a62a84e4 100644 (file)
@@ -19,7 +19,7 @@ from .relocation import RelocationSection, RelocationHandler
 from .gnuversions import (
         GNUVerNeedSection, GNUVerDefSection,
         GNUVerSymSection)
-from .segments import Segment, InterpSegment
+from .segments import Segment, InterpSegment, NoteSegment
 from ..dwarf.dwarfinfo import DWARFInfo, DebugSectionDescriptor, DwarfConfig
 
 
@@ -232,6 +232,8 @@ class ELFFile(object):
             return InterpSegment(segment_header, self.stream)
         elif segtype == 'PT_DYNAMIC':
             return DynamicSegment(segment_header, self.stream, self)
+        elif segtype == 'PT_NOTE':
+            return NoteSegment(segment_header, self.stream, self)
         else:
             return Segment(segment_header, self.stream)
 
@@ -372,5 +374,3 @@ class ELFFile(object):
                 name=section.name,
                 global_offset=section['sh_offset'],
                 size=section['sh_size'])
-
-
index 4aa449a8fe5a04e11c857909aeeb8c816af616f6..ea4dcf157072f00d0052e10be6d5e998c7de6a38 100644 (file)
@@ -488,6 +488,26 @@ ENUM_SUNW_SYMINFO_BOUNDTO = dict(
     _default_=Pass,
 )
 
+# PT_NOTE section types
+ENUM_NOTE_N_TYPE = dict(
+    NT_GNU_ABI_TAG=1,
+    NT_GNU_HWCAP=2,
+    NT_GNU_BUILD_ID=3,
+    NT_GNU_GOLD_VERSION=4,
+    _default_=Pass,
+)
+
+# Values in GNU .note.ABI-tag notes (n_type=='NT_GNU_ABI_TAG')
+ENUM_NOTE_ABI_TAG_OS = dict(
+    ELF_NOTE_OS_LINUX=0,
+    ELF_NOTE_OS_GNU=1,
+    ELF_NOTE_OS_SOLARIS2=2,
+    ELF_NOTE_OS_FREEBSD=3,
+    ELF_NOTE_OS_NETBSD=4,
+    ELF_NOTE_OS_SYLLABLE=5,
+    _default_=Pass,
+)
+
 ENUM_RELOC_TYPE_ARM = dict(
     R_ARM_NONE=0,
     R_ARM_PC24=1,
index bc54da2c0563da6a845f5ed94962d0f391fa9b23..8b98def516a8c300e4e18d8b4ce8b14e83f341b6 100644 (file)
@@ -7,7 +7,8 @@
 # This code is in the public domain
 #-------------------------------------------------------------------------------
 from ..construct import CString
-from ..common.utils import struct_parse
+from ..common.utils import roundup, struct_parse
+from ..common.py3compat import bytes2str
 from .constants import SH_FLAGS
 
 
@@ -96,3 +97,41 @@ class InterpSegment(Segment):
             stream_pos=path_offset)
 
 
+class NoteSegment(Segment):
+    """ NOTE segment. Knows how to parse notes.
+    """
+    def __init__(self, header, stream, elffile):
+        super(NoteSegment, self).__init__(header, stream)
+        self._elfstructs = elffile.structs
+
+    def iter_notes(self):
+        """ Iterates the list of notes in the segment.
+        """
+        offset = self['p_offset']
+        end = self['p_offset'] + self['p_filesz']
+        while offset < end:
+            note = struct_parse(
+                self._elfstructs.Elf_Nhdr,
+                self.stream,
+                stream_pos=offset)
+            note['n_offset'] = offset
+            offset += self._elfstructs.Elf_Nhdr.sizeof()
+            self.stream.seek(offset)
+            # n_namesz is 4-byte aligned.
+            disk_namesz = roundup(note['n_namesz'], 2)
+            note['n_name'] = bytes2str(
+                CString('').parse(self.stream.read(disk_namesz)))
+            offset += disk_namesz
+
+            desc_data = bytes2str(self.stream.read(note['n_descsz']))
+            if note['n_type'] == 'NT_GNU_ABI_TAG':
+                note['n_desc'] = struct_parse(self._elfstructs.Elf_Nhdr_abi,
+                                              self.stream,
+                                              offset)
+            elif note['n_type'] == 'NT_GNU_BUILD_ID':
+                note['n_desc'] = ''.join('%.2x' % ord(b) for b in desc_data)
+            else:
+                note['n_desc'] = desc_data
+            offset += roundup(note['n_descsz'], 2)
+            note['n_size'] = offset - note['n_offset']
+            yield note
index 0862400faebee028ec3b95f4f66511ed02ca2989..d2404ab7b5ac3267d96dbb48e33490f849089f84 100644 (file)
@@ -77,6 +77,7 @@ class ELFStructs(object):
         self._create_gnu_verneed()
         self._create_gnu_verdef()
         self._create_gnu_versym()
+        self._create_note()
 
     def _create_ehdr(self):
         self.Elf_Ehdr = Struct('Elf_Ehdr',
@@ -254,3 +255,17 @@ class ELFStructs(object):
         self.Elf_Versym = Struct('Elf_Versym',
             Enum(self.Elf_half('ndx'), **ENUM_VERSYM),
         )
+
+    def _create_note(self):
+        # Structure of "PT_NOTE" section
+        self.Elf_Nhdr = Struct('Elf_Nhdr',
+            self.Elf_word('n_namesz'),
+            self.Elf_word('n_descsz'),
+            Enum(self.Elf_word('n_type'), **ENUM_NOTE_N_TYPE),
+        )
+        self.Elf_Nhdr_abi = Struct('Elf_Nhdr_abi',
+            Enum(self.Elf_word('abi_os'), **ENUM_NOTE_ABI_TAG_OS),
+            self.Elf_word('abi_major'),
+            self.Elf_word('abi_minor'),
+            self.Elf_word('abi_tiny'),
+        )
index 8179c013a54cf5ca7a369c386bf3b571fd894823..967fb0e1d681f6e9fe3d27a95d5a93e8bc6a8459 100755 (executable)
@@ -23,7 +23,7 @@ from elftools.common.py3compat import (
 from elftools.elf.elffile import ELFFile
 from elftools.elf.dynamic import DynamicSection, DynamicSegment
 from elftools.elf.enums import ENUM_D_TAG
-from elftools.elf.segments import InterpSegment
+from elftools.elf.segments import InterpSegment, NoteSegment
 from elftools.elf.sections import SymbolTableSection
 from elftools.elf.gnuversions import (
     GNUVerSymSection, GNUVerDefSection,
@@ -37,7 +37,7 @@ from elftools.elf.descriptions import (
     describe_sh_type, describe_sh_flags,
     describe_symbol_type, describe_symbol_bind, describe_symbol_visibility,
     describe_symbol_shndx, describe_reloc_type, describe_dyn_tag,
-    describe_ver_flags,
+    describe_ver_flags, describe_note
     )
 from elftools.elf.constants import E_FLAGS
 from elftools.dwarf.dwarfinfo import DWARFInfo
@@ -373,6 +373,23 @@ class ReadElf(object):
             if self.elffile.num_segments():
                 self._emitline("\nThere is no dynamic section in this file.")
 
+    def display_notes(self):
+        """ Display the notes contained in the file
+        """
+        for segment in self.elffile.iter_segments():
+            if isinstance(segment, NoteSegment):
+                for note in segment.iter_notes():
+                      self._emitline(
+                          "\nDisplaying notes found at file offset "
+                          "%s with length %s:" % (
+                              self._format_hex(note['n_offset'], fieldsize=8),
+                              self._format_hex(note['n_size'], fieldsize=8)))
+                      self._emitline('  Owner                 Data size        Description')
+                      self._emitline('  %s%s %s\t%s' % (
+                          note['n_name'], ' ' * (20 - len(note['n_name'])),
+                          self._format_hex(note['n_descsz'], fieldsize=8),
+                          describe_note(note)))
+
     def display_relocations(self):
         """ Display the relocations contained in the file
         """
@@ -446,8 +463,8 @@ class ReadElf(object):
                     section, 'Version symbols', lead0x=False)
 
                 num_symbols = section.num_symbols()
-    
-                # Symbol version info are printed four by four entries 
+
+                # Symbol version info are printed four by four entries
                 for idx_by_4 in range(0, num_symbols, 4):
 
                     self._emit('  %03x:' % idx_by_4)
@@ -1055,6 +1072,9 @@ def main(stream=None):
     optparser.add_option('-s', '--symbols', '--syms',
             action='store_true', dest='show_symbols',
             help='Display the symbol table')
+    optparser.add_option('-n', '--notes',
+            action='store_true', dest='show_notes',
+            help='Display the core notes (if present)')
     optparser.add_option('-r', '--relocs',
             action='store_true', dest='show_relocs',
             help='Display the relocations (if present)')
@@ -1101,6 +1121,8 @@ def main(stream=None):
                 readelf.display_dynamic_tags()
             if options.show_symbols:
                 readelf.display_symbol_tables()
+            if options.show_notes:
+                readelf.display_notes()
             if options.show_relocs:
                 readelf.display_relocations()
             if options.show_version_info:
@@ -1133,5 +1155,3 @@ def profile_main():
 if __name__ == '__main__':
     main()
     #profile_main()
-
-
index 2c1d5f3992db203772b5cf860452f39c49c7fbaf..88869bedf75a65009056415f25ffe531887fbbce 100755 (executable)
@@ -46,7 +46,7 @@ def run_test_on_file(filename, verbose=False):
     success = True
     testlog.info("Test file '%s'" % filename)
     for option in [
-            '-e', '-d', '-s', '-r', '-x.text', '-p.shstrtab', '-V',
+            '-e', '-d', '-s', '-n', '-r', '-x.text', '-p.shstrtab', '-V',
             '--debug-dump=info', '--debug-dump=decodedline',
             '--debug-dump=frames', '--debug-dump=frames-interp']:
         if verbose: testlog.info("..option='%s'" % option)
@@ -212,4 +212,3 @@ def main():
 
 if __name__ == '__main__':
     sys.exit(main())
-