xref: /aosp_15_r20/system/apex/tools/apex_elf_checker.py (revision 33f3758387333dbd2962d7edbd98681940d895da)
1*33f37583SAndroid Build Coastguard Worker#!/usr/bin/env python
2*33f37583SAndroid Build Coastguard Worker#
3*33f37583SAndroid Build Coastguard Worker# Copyright (C) 2023 The Android Open Source Project
4*33f37583SAndroid Build Coastguard Worker#
5*33f37583SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*33f37583SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*33f37583SAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*33f37583SAndroid Build Coastguard Worker#
9*33f37583SAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
10*33f37583SAndroid Build Coastguard Worker#
11*33f37583SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*33f37583SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*33f37583SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*33f37583SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*33f37583SAndroid Build Coastguard Worker# limitations under the License.
16*33f37583SAndroid Build Coastguard Worker"""apex_elf_checker checks if ELF files in the APEX
17*33f37583SAndroid Build Coastguard Worker
18*33f37583SAndroid Build Coastguard WorkerUsage: apex_elf_checker [--unwanted <names>] <apex>
19*33f37583SAndroid Build Coastguard Worker
20*33f37583SAndroid Build Coastguard Worker  --unwanted <names>
21*33f37583SAndroid Build Coastguard Worker
22*33f37583SAndroid Build Coastguard Worker    Fail if any of ELF files in APEX has any of unwanted names in NEEDED `
23*33f37583SAndroid Build Coastguard Worker"""
24*33f37583SAndroid Build Coastguard Worker
25*33f37583SAndroid Build Coastguard Workerimport argparse
26*33f37583SAndroid Build Coastguard Workerimport os
27*33f37583SAndroid Build Coastguard Workerimport re
28*33f37583SAndroid Build Coastguard Workerimport subprocess
29*33f37583SAndroid Build Coastguard Workerimport sys
30*33f37583SAndroid Build Coastguard Workerimport tempfile
31*33f37583SAndroid Build Coastguard Worker
32*33f37583SAndroid Build Coastguard Worker
33*33f37583SAndroid Build Coastguard Worker_DYNAMIC_SECTION_NEEDED_PATTERN = re.compile(
34*33f37583SAndroid Build Coastguard Worker    '^  0x[0-9a-fA-F]+\\s+NEEDED\\s+Shared library: \\[(.*)\\]$'
35*33f37583SAndroid Build Coastguard Worker)
36*33f37583SAndroid Build Coastguard Worker
37*33f37583SAndroid Build Coastguard Worker
38*33f37583SAndroid Build Coastguard Worker_ELF_MAGIC = b'\x7fELF'
39*33f37583SAndroid Build Coastguard Worker
40*33f37583SAndroid Build Coastguard Worker
41*33f37583SAndroid Build Coastguard Workerdef ParseArgs():
42*33f37583SAndroid Build Coastguard Worker  parser = argparse.ArgumentParser()
43*33f37583SAndroid Build Coastguard Worker  parser.add_argument('apex', help='Path to the APEX')
44*33f37583SAndroid Build Coastguard Worker  parser.add_argument(
45*33f37583SAndroid Build Coastguard Worker      '--tool_path',
46*33f37583SAndroid Build Coastguard Worker      help='Tools are searched in TOOL_PATH/bin. Colon-separated list of paths',
47*33f37583SAndroid Build Coastguard Worker  )
48*33f37583SAndroid Build Coastguard Worker  parser.add_argument(
49*33f37583SAndroid Build Coastguard Worker      '--unwanted',
50*33f37583SAndroid Build Coastguard Worker      help='Names not allowed in DT_NEEDED. Colon-separated list of names',
51*33f37583SAndroid Build Coastguard Worker  )
52*33f37583SAndroid Build Coastguard Worker  return parser.parse_args()
53*33f37583SAndroid Build Coastguard Worker
54*33f37583SAndroid Build Coastguard Worker
55*33f37583SAndroid Build Coastguard Workerdef InitTools(tool_path):
56*33f37583SAndroid Build Coastguard Worker  if tool_path is None:
57*33f37583SAndroid Build Coastguard Worker    exec_path = os.path.realpath(sys.argv[0])
58*33f37583SAndroid Build Coastguard Worker    if exec_path.endswith('.py'):
59*33f37583SAndroid Build Coastguard Worker      script_name = os.path.basename(exec_path)[:-3]
60*33f37583SAndroid Build Coastguard Worker      sys.exit(
61*33f37583SAndroid Build Coastguard Worker          f'Do not invoke {exec_path} directly. Instead, use {script_name}'
62*33f37583SAndroid Build Coastguard Worker      )
63*33f37583SAndroid Build Coastguard Worker    tool_path = os.environ['PATH']
64*33f37583SAndroid Build Coastguard Worker
65*33f37583SAndroid Build Coastguard Worker  def ToolPath(name):
66*33f37583SAndroid Build Coastguard Worker    for p in tool_path.split(':'):
67*33f37583SAndroid Build Coastguard Worker      path = os.path.join(p, name)
68*33f37583SAndroid Build Coastguard Worker      if os.path.exists(path):
69*33f37583SAndroid Build Coastguard Worker        return path
70*33f37583SAndroid Build Coastguard Worker    sys.exit(f'Required tool({name}) not found in {tool_path}')
71*33f37583SAndroid Build Coastguard Worker
72*33f37583SAndroid Build Coastguard Worker  return {
73*33f37583SAndroid Build Coastguard Worker      tool: ToolPath(tool)
74*33f37583SAndroid Build Coastguard Worker      for tool in [
75*33f37583SAndroid Build Coastguard Worker          'deapexer',
76*33f37583SAndroid Build Coastguard Worker          'debugfs_static',
77*33f37583SAndroid Build Coastguard Worker          'fsck.erofs',
78*33f37583SAndroid Build Coastguard Worker          'llvm-readelf',
79*33f37583SAndroid Build Coastguard Worker      ]
80*33f37583SAndroid Build Coastguard Worker  }
81*33f37583SAndroid Build Coastguard Worker
82*33f37583SAndroid Build Coastguard Worker
83*33f37583SAndroid Build Coastguard Workerdef IsElfFile(path):
84*33f37583SAndroid Build Coastguard Worker  with open(path, 'rb') as f:
85*33f37583SAndroid Build Coastguard Worker    buf = bytearray(len(_ELF_MAGIC))
86*33f37583SAndroid Build Coastguard Worker    f.readinto(buf)
87*33f37583SAndroid Build Coastguard Worker    return buf == _ELF_MAGIC
88*33f37583SAndroid Build Coastguard Worker
89*33f37583SAndroid Build Coastguard Worker
90*33f37583SAndroid Build Coastguard Workerdef ParseElfNeeded(path, tools):
91*33f37583SAndroid Build Coastguard Worker  output = subprocess.check_output(
92*33f37583SAndroid Build Coastguard Worker      [tools['llvm-readelf'], '-d', '--elf-output-style', 'LLVM', path],
93*33f37583SAndroid Build Coastguard Worker      text=True,
94*33f37583SAndroid Build Coastguard Worker      stderr=subprocess.PIPE,
95*33f37583SAndroid Build Coastguard Worker  )
96*33f37583SAndroid Build Coastguard Worker
97*33f37583SAndroid Build Coastguard Worker  needed = []
98*33f37583SAndroid Build Coastguard Worker  for line in output.splitlines():
99*33f37583SAndroid Build Coastguard Worker    match = _DYNAMIC_SECTION_NEEDED_PATTERN.match(line)
100*33f37583SAndroid Build Coastguard Worker    if match:
101*33f37583SAndroid Build Coastguard Worker      needed.append(match.group(1))
102*33f37583SAndroid Build Coastguard Worker  return needed
103*33f37583SAndroid Build Coastguard Worker
104*33f37583SAndroid Build Coastguard Worker
105*33f37583SAndroid Build Coastguard Workerdef ScanElfFiles(work_dir):
106*33f37583SAndroid Build Coastguard Worker  for parent, _, files in os.walk(work_dir):
107*33f37583SAndroid Build Coastguard Worker    for file in files:
108*33f37583SAndroid Build Coastguard Worker      path = os.path.join(parent, file)
109*33f37583SAndroid Build Coastguard Worker      # Skip symlinks for APEXes with symlink optimization
110*33f37583SAndroid Build Coastguard Worker      if os.path.islink(path):
111*33f37583SAndroid Build Coastguard Worker        continue
112*33f37583SAndroid Build Coastguard Worker      if IsElfFile(path):
113*33f37583SAndroid Build Coastguard Worker        yield path
114*33f37583SAndroid Build Coastguard Worker
115*33f37583SAndroid Build Coastguard Worker
116*33f37583SAndroid Build Coastguard Workerdef CheckElfFiles(args, tools):
117*33f37583SAndroid Build Coastguard Worker  with tempfile.TemporaryDirectory() as work_dir:
118*33f37583SAndroid Build Coastguard Worker    subprocess.check_output(
119*33f37583SAndroid Build Coastguard Worker        [
120*33f37583SAndroid Build Coastguard Worker            tools['deapexer'],
121*33f37583SAndroid Build Coastguard Worker            '--debugfs_path',
122*33f37583SAndroid Build Coastguard Worker            tools['debugfs_static'],
123*33f37583SAndroid Build Coastguard Worker            '--fsckerofs_path',
124*33f37583SAndroid Build Coastguard Worker            tools['fsck.erofs'],
125*33f37583SAndroid Build Coastguard Worker            'extract',
126*33f37583SAndroid Build Coastguard Worker            args.apex,
127*33f37583SAndroid Build Coastguard Worker            work_dir,
128*33f37583SAndroid Build Coastguard Worker        ],
129*33f37583SAndroid Build Coastguard Worker        text=True,
130*33f37583SAndroid Build Coastguard Worker        stderr=subprocess.PIPE,
131*33f37583SAndroid Build Coastguard Worker    )
132*33f37583SAndroid Build Coastguard Worker
133*33f37583SAndroid Build Coastguard Worker    if args.unwanted:
134*33f37583SAndroid Build Coastguard Worker      unwanted = set(args.unwanted.split(':'))
135*33f37583SAndroid Build Coastguard Worker      for file in ScanElfFiles(work_dir):
136*33f37583SAndroid Build Coastguard Worker        needed = set(ParseElfNeeded(file, tools))
137*33f37583SAndroid Build Coastguard Worker        if unwanted & needed:
138*33f37583SAndroid Build Coastguard Worker          sys.exit(
139*33f37583SAndroid Build Coastguard Worker              f'{os.path.relpath(file, work_dir)} has unwanted NEEDED:'
140*33f37583SAndroid Build Coastguard Worker              f' {",".join(unwanted & needed)}'
141*33f37583SAndroid Build Coastguard Worker          )
142*33f37583SAndroid Build Coastguard Worker
143*33f37583SAndroid Build Coastguard Worker
144*33f37583SAndroid Build Coastguard Workerdef main():
145*33f37583SAndroid Build Coastguard Worker  args = ParseArgs()
146*33f37583SAndroid Build Coastguard Worker  tools = InitTools(args.tool_path)
147*33f37583SAndroid Build Coastguard Worker  try:
148*33f37583SAndroid Build Coastguard Worker    CheckElfFiles(args, tools)
149*33f37583SAndroid Build Coastguard Worker  except subprocess.CalledProcessError as e:
150*33f37583SAndroid Build Coastguard Worker    sys.exit('Result:' + str(e.stderr))
151*33f37583SAndroid Build Coastguard Worker
152*33f37583SAndroid Build Coastguard Worker
153*33f37583SAndroid Build Coastguard Workerif __name__ == '__main__':
154*33f37583SAndroid Build Coastguard Worker  main()
155