extract_partition.py 5.4 KB

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