tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

extract_partition.py (5058B)


      1 #!/usr/bin/env python3
      2 # Copyright 2019 The Chromium Authors
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 """Extracts an LLD partition from an ELF file."""
      6 
      7 import argparse
      8 import hashlib
      9 import os
     10 import struct
     11 import subprocess
     12 import sys
     13 import tempfile
     14 
     15 
     16 def _ComputeNewBuildId(old_build_id, file_path):
     17  """
     18    Computes the new build-id from old build-id and file_path.
     19 
     20    Args:
     21      old_build_id: Original build-id in bytearray.
     22      file_path: Path to output ELF file.
     23 
     24    Returns:
     25      New build id with the same length as |old_build_id|.
     26    """
     27  m = hashlib.sha256()
     28  m.update(old_build_id)
     29  m.update(os.path.basename(file_path).encode('utf-8'))
     30  hash_bytes = m.digest()
     31  # In case build_id is longer than hash computed, repeat the hash
     32  # to the desired length first.
     33  id_size = len(old_build_id)
     34  hash_size = len(hash_bytes)
     35  return (hash_bytes * (id_size // hash_size + 1))[:id_size]
     36 
     37 
     38 def _ExtractPartition(objcopy, input_elf, output_elf, partition):
     39  """
     40  Extracts a partition from an ELF file.
     41 
     42  For partitions other than main partition, we need to rewrite
     43  the .note.gnu.build-id section so that the build-id remains
     44  unique.
     45 
     46  Note:
     47  - `objcopy` does not modify build-id when partitioning the
     48    combined ELF file by default.
     49  - The new build-id is calculated as hash of original build-id
     50    and partitioned ELF file name.
     51 
     52  Args:
     53    objcopy: Path to objcopy binary.
     54    input_elf: Path to input ELF file.
     55    output_elf: Path to output ELF file.
     56    partition: Partition to extract from combined ELF file. None when
     57      extracting main partition.
     58  """
     59  if not partition:  # main partition
     60    # We do not overwrite build-id on main partition to allow the expected
     61    # partition build ids to be synthesized given a libchrome.so binary,
     62    # if necessary.
     63    subprocess.check_call(
     64        [objcopy, '--extract-main-partition', input_elf, output_elf])
     65    return
     66 
     67  # partitioned libs
     68  build_id_section = '.note.gnu.build-id'
     69 
     70  with tempfile.TemporaryDirectory() as tempdir:
     71    temp_elf = os.path.join(tempdir, 'obj_without_id.so')
     72    old_build_id_file = os.path.join(tempdir, 'old_build_id')
     73    new_build_id_file = os.path.join(tempdir, 'new_build_id')
     74 
     75    # Dump out build-id section.
     76    subprocess.check_call([
     77        objcopy,
     78        '--extract-partition',
     79        partition,
     80        '--dump-section',
     81        '{}={}'.format(build_id_section, old_build_id_file),
     82        input_elf,
     83        temp_elf,
     84    ])
     85 
     86    with open(old_build_id_file, 'rb') as f:
     87      note_content = f.read()
     88 
     89    # .note section has following format according to <elf/external.h>
     90    #   typedef struct {
     91    #       unsigned char   namesz[4];  /* Size of entry's owner string */
     92    #       unsigned char   descsz[4];  /* Size of the note descriptor */
     93    #       unsigned char   type[4];    /* Interpretation of the descriptor */
     94    #       char        name[1];        /* Start of the name+desc data */
     95    #   } Elf_External_Note;
     96    # `build-id` rewrite is only required on Android platform,
     97    # where we have partitioned lib.
     98    # Android platform uses little-endian.
     99    # <: little-endian
    100    # 4x: Skip 4 bytes
    101    # L: unsigned long, 4 bytes
    102    descsz, = struct.Struct('<4xL').unpack_from(note_content)
    103    prefix = note_content[:-descsz]
    104    build_id = note_content[-descsz:]
    105 
    106    with open(new_build_id_file, 'wb') as f:
    107      f.write(prefix + _ComputeNewBuildId(build_id, output_elf))
    108 
    109    # Update the build-id section.
    110    subprocess.check_call([
    111        objcopy,
    112        '--update-section',
    113        '{}={}'.format(build_id_section, new_build_id_file),
    114        temp_elf,
    115        output_elf,
    116    ])
    117 
    118 
    119 def main():
    120  parser = argparse.ArgumentParser(description=__doc__)
    121  parser.add_argument(
    122      '--partition',
    123      help='Name of partition if not the main partition',
    124      metavar='PART')
    125  parser.add_argument(
    126      '--objcopy',
    127      required=True,
    128      help='Path to llvm-objcopy binary',
    129      metavar='FILE')
    130  parser.add_argument(
    131      '--unstripped-output',
    132      required=True,
    133      help='Unstripped output file',
    134      metavar='FILE')
    135  parser.add_argument(
    136      '--stripped-output',
    137      required=True,
    138      help='Stripped output file',
    139      metavar='FILE')
    140  parser.add_argument('--split-dwarf', action='store_true')
    141  parser.add_argument('input', help='Input file')
    142  args = parser.parse_args()
    143 
    144  _ExtractPartition(args.objcopy, args.input, args.unstripped_output,
    145                    args.partition)
    146  subprocess.check_call([
    147      args.objcopy,
    148      '--strip-all',
    149      args.unstripped_output,
    150      args.stripped_output,
    151  ])
    152 
    153  # Debug info for partitions is the same as for the main library, so just
    154  # symlink the .dwp files.
    155  if args.split_dwarf:
    156    dest = args.unstripped_output + '.dwp'
    157    try:
    158      os.unlink(dest)
    159    except OSError:
    160      pass
    161    relpath = os.path.relpath(args.input + '.dwp', os.path.dirname(dest))
    162    os.symlink(relpath, dest)
    163 
    164 
    165 if __name__ == '__main__':
    166  sys.exit(main())