• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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