xref: /aosp_15_r20/external/angle/build/extract_partition.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
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