77 lines
2.8 KiB
Python
Executable File
77 lines
2.8 KiB
Python
Executable File
#!/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()
|