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 os 10import struct 11import subprocess 12import sys 13import tempfile 14 15 16def _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 38def _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 119def 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 165if __name__ == '__main__': 166 sys.exit(main()) 167