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