#!/usr/bin/env python3 # # Copyright (C) 2021 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import argparse, subprocess, re, os, glob, array, gzip DESCRIPTION = "This tool reduces ELF size using stripping and compression" STRIP_SECTIONS = [".text", ".rodata"] READELF_FORMAT = """ \s+(?P[0-9\[\] ]+) \s+(?P[a-z_.]+) \s+(?P[A-Z_]+) \s+(?P
[0-9a-f]+) \s+(?P[0-9a-f]+) \s+(?P[0-9a-f]+) """ def strip(path): proc = subprocess.run(["readelf", "--file-header", "--sections", path], stdout=subprocess.PIPE, universal_newlines=True) assert(proc.returncode == 0) # readelf command failed sections = {m["name"] : m for m in re.finditer(READELF_FORMAT, proc.stdout, re.VERBOSE)} for name in STRIP_SECTIONS: if name == ".text" and os.path.basename(path) in ["vdso", "vdso.so", "libc.so"]: continue # Stripping these libraries breaks signal handler unwinding. section = sections.get(name) if not section: print("Warning: {} not found in {}".format(name, path)) if section and section["type"] != "NOBITS": offset, size = int(section["offset"], 16), int(section["size"], 16) & ~1 with open(path, "r+b") as f: f.seek(offset) data = array.array('H') # 16-bit unsigned integer array. data.frombytes(f.read(size)) # Preserve top bits for thumb so that we can still determine instruction size. is_thumb = (name == ".text" and re.search("Machine:\s+ARM", proc.stdout)) for i in range(len(data)): data[i] = 0xffff if is_thumb and (data[i] & 0xe000) == 0xe000 else 0 f.seek(offset) f.write(data.tobytes()) # gzip-compress the file to take advantage of the zeroed sections. with open(path, 'rb') as src, gzip.open(path + ".gz", 'wb') as dst: dst.write(src.read()) os.remove(path) def main(): parser = argparse.ArgumentParser(description=DESCRIPTION) parser.add_argument('target', nargs='+', help="ELF file or whole directory to strip") args = parser.parse_args() for path in args.target: if os.path.isdir(path): for path in glob.glob(os.path.join(path, "**/*"), recursive=True): if os.path.isfile(path) and open(path, "rb").read(4) == b"\x7FELF": strip(path) else: strip(path) if __name__ == '__main__': main()