blob: bb47de794635f00a348f707104f384844e86a650 [file] [log] [blame]
#!/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<index>[0-9\[\] ]+)
\s+(?P<name>[a-z_.]+)
\s+(?P<type>[A-Z_]+)
\s+(?P<address>[0-9a-f]+)
\s+(?P<offset>[0-9a-f]+)
\s+(?P<size>[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()