blob: 8f51b0ec4bf5fb5f0358f7113a184dba1dcd5ea9 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright (C) 2023 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.
"""apex_elf_checker checks if ELF files in the APEX
Usage: apex_elf_checker [--unwanted <names>] <apex>
--unwanted <names>
Fail if any of ELF files in APEX has any of unwanted names in NEEDED `
"""
import argparse
import os
import re
import subprocess
import sys
import tempfile
_DYNAMIC_SECTION_NEEDED_PATTERN = re.compile(
'^ 0x[0-9a-fA-F]+\\s+NEEDED\\s+Shared library: \\[(.*)\\]$'
)
_ELF_MAGIC = b'\x7fELF'
def ParseArgs():
parser = argparse.ArgumentParser()
parser.add_argument('apex', help='Path to the APEX')
parser.add_argument(
'--tool_path',
help='Tools are searched in TOOL_PATH/bin. Colon-separated list of paths',
)
parser.add_argument(
'--unwanted',
help='Names not allowed in DT_NEEDED. Colon-separated list of names',
)
return parser.parse_args()
def InitTools(tool_path):
if tool_path is None:
exec_path = os.path.realpath(sys.argv[0])
if exec_path.endswith('.py'):
script_name = os.path.basename(exec_path)[:-3]
sys.exit(
f'Do not invoke {exec_path} directly. Instead, use {script_name}'
)
tool_path = os.environ['PATH']
def ToolPath(name):
for p in tool_path.split(':'):
path = os.path.join(p, name)
if os.path.exists(path):
return path
sys.exit(f'Required tool({name}) not found in {tool_path}')
return {
tool: ToolPath(tool)
for tool in [
'deapexer',
'debugfs_static',
'fsck.erofs',
'llvm-readelf',
]
}
def IsElfFile(path):
with open(path, 'rb') as f:
buf = bytearray(len(_ELF_MAGIC))
f.readinto(buf)
return buf == _ELF_MAGIC
def ParseElfNeeded(path, tools):
output = subprocess.check_output(
[tools['llvm-readelf'], '-d', '--elf-output-style', 'LLVM', path],
text=True,
stderr=subprocess.PIPE,
)
needed = []
for line in output.splitlines():
match = _DYNAMIC_SECTION_NEEDED_PATTERN.match(line)
if match:
needed.append(match.group(1))
return needed
def ScanElfFiles(work_dir):
for parent, _, files in os.walk(work_dir):
for file in files:
path = os.path.join(parent, file)
# Skip symlinks for APEXes with symlink optimization
if os.path.islink(path):
continue
if IsElfFile(path):
yield path
def CheckElfFiles(args, tools):
with tempfile.TemporaryDirectory() as work_dir:
subprocess.check_output(
[
tools['deapexer'],
'--debugfs_path',
tools['debugfs_static'],
'--fsckerofs_path',
tools['fsck.erofs'],
'extract',
args.apex,
work_dir,
],
text=True,
stderr=subprocess.PIPE,
)
if args.unwanted:
unwanted = set(args.unwanted.split(':'))
for file in ScanElfFiles(work_dir):
needed = set(ParseElfNeeded(file, tools))
if unwanted & needed:
sys.exit(
f'{os.path.relpath(file, work_dir)} has unwanted NEEDED:'
f' {",".join(unwanted & needed)}'
)
def main():
args = ParseArgs()
tools = InitTools(args.tool_path)
try:
CheckElfFiles(args, tools)
except subprocess.CalledProcessError as e:
sys.exit('Result:' + str(e.stderr))
if __name__ == '__main__':
main()