xref: /aosp_15_r20/system/unwinding/libunwindstack/tools/strip.py (revision eb293b8f56ee8303637c5595cfcdeef8039e85c6)
1#!/usr/bin/env python3
2#
3# Copyright (C) 2021 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import argparse, subprocess, re, os, glob, array, gzip
19
20DESCRIPTION = "This tool reduces ELF size using stripping and compression"
21
22STRIP_SECTIONS = [".text", ".rodata"]
23
24READELF_FORMAT = """
25  \s+(?P<index>[0-9\[\] ]+)
26  \s+(?P<name>[a-z_.]+)
27  \s+(?P<type>[A-Z_]+)
28  \s+(?P<address>[0-9a-f]+)
29  \s+(?P<offset>[0-9a-f]+)
30  \s+(?P<size>[0-9a-f]+)
31"""
32
33def strip(path):
34  proc = subprocess.run(["readelf", "--file-header", "--sections", path],
35                        stdout=subprocess.PIPE, universal_newlines=True)
36  assert(proc.returncode == 0)  # readelf command failed
37  sections = {m["name"] : m for m in re.finditer(READELF_FORMAT, proc.stdout, re.VERBOSE)}
38  for name in STRIP_SECTIONS:
39    if name == ".text" and os.path.basename(path) in ["vdso", "vdso.so", "libc.so"]:
40      continue  # Stripping these libraries breaks signal handler unwinding.
41    section = sections.get(name)
42    if not section:
43      print("Warning: {} not found in {}".format(name, path))
44    if section and section["type"] != "NOBITS":
45      offset, size = int(section["offset"], 16), int(section["size"], 16) & ~1
46      with open(path, "r+b") as f:
47        f.seek(offset)
48        data = array.array('H')  # 16-bit unsigned integer array.
49        data.frombytes(f.read(size))
50        # Preserve top bits for thumb so that we can still determine instruction size.
51        is_thumb = (name == ".text" and re.search("Machine:\s+ARM", proc.stdout))
52        for i in range(len(data)):
53          data[i] = 0xffff if is_thumb and (data[i] & 0xe000) == 0xe000 else 0
54        f.seek(offset)
55        f.write(data.tobytes())
56
57  # gzip-compress the file to take advantage of the zeroed sections.
58  with open(path, 'rb') as src, gzip.open(path + ".gz", 'wb') as dst:
59    dst.write(src.read())
60  os.remove(path)
61
62def main():
63  parser = argparse.ArgumentParser(description=DESCRIPTION)
64  parser.add_argument('target', nargs='+', help="ELF file or whole directory to strip")
65  args = parser.parse_args()
66
67  for path in args.target:
68    if os.path.isdir(path):
69      for path in glob.glob(os.path.join(path, "**/*"), recursive=True):
70        if os.path.isfile(path) and open(path, "rb").read(4) == b"\x7FELF":
71          strip(path)
72    else:
73      strip(path)
74
75if __name__ == '__main__':
76  main()
77