From 4228ea065543ceaf4c8e679af2c7323d1bb12043 Mon Sep 17 00:00:00 2001 From: Alex Deymo Date: Sun, 17 Aug 2014 13:15:46 -0700 Subject: [PATCH] Parse PT_NOTE segment and expose it with the new '-n' option to readelf. 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 | 7 +++++- elftools/elf/descriptions.py | 34 ++++++++++++++++++++++++++++++ elftools/elf/elffile.py | 6 +++--- elftools/elf/enums.py | 20 ++++++++++++++++++ elftools/elf/segments.py | 41 +++++++++++++++++++++++++++++++++++- elftools/elf/structs.py | 15 +++++++++++++ scripts/readelf.py | 32 ++++++++++++++++++++++------ test/run_readelf_tests.py | 3 +-- 8 files changed, 145 insertions(+), 13 deletions(-) diff --git a/elftools/common/utils.py b/elftools/common/utils.py index a2c9edb..d8acefa 100644 --- a/elftools/common/utils.py +++ b/elftools/common/utils.py @@ -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) - diff --git a/elftools/elf/descriptions.py b/elftools/elf/descriptions.py index 6d108c2..a25e51b 100644 --- a/elftools/elf/descriptions.py +++ b/elftools/elf/descriptions.py @@ -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 = '' @@ -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)) diff --git a/elftools/elf/elffile.py b/elftools/elf/elffile.py index 15aa07e..4132c1e 100644 --- a/elftools/elf/elffile.py +++ b/elftools/elf/elffile.py @@ -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']) - - diff --git a/elftools/elf/enums.py b/elftools/elf/enums.py index 4aa449a..ea4dcf1 100644 --- a/elftools/elf/enums.py +++ b/elftools/elf/enums.py @@ -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, diff --git a/elftools/elf/segments.py b/elftools/elf/segments.py index bc54da2..8b98def 100644 --- a/elftools/elf/segments.py +++ b/elftools/elf/segments.py @@ -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 diff --git a/elftools/elf/structs.py b/elftools/elf/structs.py index 0862400..d2404ab 100644 --- a/elftools/elf/structs.py +++ b/elftools/elf/structs.py @@ -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'), + ) diff --git a/scripts/readelf.py b/scripts/readelf.py index 8179c01..967fb0e 100755 --- a/scripts/readelf.py +++ b/scripts/readelf.py @@ -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() - - diff --git a/test/run_readelf_tests.py b/test/run_readelf_tests.py index 2c1d5f3..88869be 100755 --- a/test/run_readelf_tests.py +++ b/test/run_readelf_tests.py @@ -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()) - -- 2.30.2