| # Copyright 2021 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Helpers that interact with the "readelf" tool.""" |
| |
| import argparse |
| import logging |
| import os |
| import re |
| import subprocess |
| |
| import archive_util |
| import models |
| import path_util |
| |
| |
| def BuildIdFromElf(elf_path): |
| """Returns the Build ID for the given binary.""" |
| args = [path_util.GetReadElfPath(), '-n', elf_path] |
| stdout = subprocess.check_output(args, encoding='ascii') |
| match = re.search(r'Build ID: (\w+)', stdout) |
| assert match, 'Build ID not found from running: ' + ' '.join(args) |
| return match.group(1) |
| |
| |
| def ArchFromElf(elf_path): |
| """Returns the GN architecture for the given binary.""" |
| args = [path_util.GetReadElfPath(), '-h', elf_path] |
| stdout = subprocess.check_output(args, encoding='ascii') |
| machine = re.search(r'Machine:\s*(.+)', stdout).group(1) |
| if machine == 'Intel 80386': |
| return 'x86' |
| if machine == 'Advanced Micro Devices X86-64': |
| return 'x64' |
| if machine == 'ARM': |
| return 'arm' |
| if machine == 'AArch64': |
| return 'arm64' |
| return machine |
| |
| |
| def SectionInfoFromElf(elf_path): |
| """Finds the address and size of all ELF sections |
| |
| Merges custom sections (those without a "." prefix) into their proceeding |
| sections. |
| |
| Returns: |
| A dict of section_name->(start_address, size). |
| """ |
| args = [path_util.GetReadElfPath(), '-S', '--wide', elf_path] |
| stdout = subprocess.check_output(args, encoding='ascii') |
| section_ranges = {} |
| # Matches [ 2] .hash HASH 00000000006681f0 0001f0 003154 04 A 3 0 8 |
| prev_section_name = None |
| for match in re.finditer(r'\[[\s\d]+\] (.*)$', stdout, re.MULTILINE): |
| items = match.group(1).split() |
| |
| section_name = items[0] |
| # The first line looks like: |
| # [ 0] NULL 00000000 000000 000000 00 0 0 0 |
| if section_name == 'NULL': |
| continue |
| assert not section_name.startswith('.debug'), ( |
| 'Should not section sizes of an unstripped binary.') |
| |
| # Stop if we hit any partitions. |
| if section_name.endswith('_partition'): |
| break |
| |
| section_type = items[1] |
| if section_type == 'NOBITS': |
| # Ensure we don't count BSS as real size. |
| assert section_name in models.BSS_SECTIONS, ( |
| 'BSS_SECTIONS out of date: ' + section_name) |
| |
| section_range = (int(items[2], 16), int(items[4], 16)) |
| |
| # E.g. Merge user-defined sections. e.g.: malloc_hook, protected_memory. |
| if not section_name.startswith('.'): |
| logging.info('Merged %s into %s', section_name, prev_section_name) |
| archive_util.ExtendSectionRangeAdjacent(section_ranges, prev_section_name, |
| section_range[0], |
| section_range[1]) |
| else: |
| section_ranges[items[0]] = section_range |
| prev_section_name = section_name |
| |
| return section_ranges |
| |
| |
| def CollectRelocationAddresses(elf_path): |
| """Returns the list of addresses that are targets for relative relocations.""" |
| cmd = [path_util.GetReadElfPath(), '--relocs', elf_path] |
| ret = subprocess.check_output(cmd, encoding='ascii').splitlines() |
| # Grab first column from (sample output) '02de6d5c 00000017 R_ARM_RELATIVE' |
| return [int(l.split(maxsplit=1)[0], 16) for l in ret if 'R_ARM_RELATIVE' in l] |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('elf_path', type=os.path.realpath) |
| args = parser.parse_args() |
| logging.basicConfig(level=logging.DEBUG, |
| format='%(levelname).1s %(relativeCreated)6d %(message)s') |
| |
| # Other functions in this file have test entrypoints in object_analyzer.py. |
| section_ranges = SectionInfoFromElf(args.elf_path) |
| for name, (addr, size) in section_ranges.items(): |
| print(f'{name:20} {addr:20x}\t{size}') |
| |
| |
| if __name__ == '__main__': |
| main() |