blob: 0c377b85604684bc20a24edfe8854ef61a1fc65c [file] [edit]
#!/usr/bin/env python3
#
# Copyright (C) 2024 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.
"""Utility function to provide a best effort dependency graph."""
import argparse
import json
import logging
import pathlib
import sys
import symbol_extraction
def find_binaries(
directory: pathlib.Path,
) -> (pathlib.Path | None, list[pathlib.Path]):
"""Locates vmlinux and kernel modules (*.ko)."""
vmlinux = list(directory.glob("**/vmlinux"))
modules = list(directory.glob("**/*.ko"))
if not vmlinux:
return None, modules
if len(vmlinux) > 1:
logging.error("More than one vmlinux found in %s", directory)
sys.exit(1)
return vmlinux[0], modules
class SetEncoder(json.JSONEncoder):
# Needed to serialize set()
def default(self, o):
if isinstance(o, set):
return list(o)
return super().default(o)
def create_graph(
undefined_symbols_by_module: dict[str, list[str]],
exported_symbols_by_module: dict[str, list[str]],
output: pathlib.Path,
):
"Creates a best effort dependency graph from symbol relationships."
ids = dict()
symbol_to_module = dict()
# Schema for the list (this uses numeric id's to reduce the output size).
# {id: {name: str, dependents: list()}}
adjacency_list = dict()
for module, exported in exported_symbols_by_module.items():
if not module in ids:
mod_id = str(len(ids))
ids[module] = mod_id
adjacency_list[mod_id] = {
"name": module,
"dependents": set(),
}
exporter = ids.get(module)
for symbol in exported:
symbol_to_module[symbol] = exporter
# Update the adjacency_list based on the links created by the undefined symbols.
for module, symbols in undefined_symbols_by_module.items():
to_id = ids.get(module)
for symbol in symbols:
if symbol not in symbol_to_module:
logging.warning("%s symbol not found in any binary.", symbol)
continue
from_id = symbol_to_module[symbol]
adjacency_list[from_id]["dependents"].add(to_id)
# Print the graph.
output.write_text(
json.dumps(adjacency_list, cls=SetEncoder), encoding="utf-8"
)
def main():
"""Extracts the required symbols for a directory full of kernel modules."""
parser = argparse.ArgumentParser()
parser.add_argument(
"directory",
type=pathlib.Path,
help="the directory to search for kernel binaries",
)
parser.add_argument(
"output",
type=pathlib.Path,
help="Path for storing the output",
)
args = parser.parse_args()
logging.basicConfig(level=logging.INFO,
format="%(levelname)s: %(message)s")
if not args.directory.is_dir():
logging.error(
"Expected a directory with binaries, but got %s", args.directory
)
return 1
# Locate the Kernel Binaries.
vmlinux, modules = find_binaries(args.directory)
# Extract undefined symbols and exported modules.
undefined_symbols_by_module = {
pathlib.Path(module).name: symbol_extraction.extract_undefined_symbols(
module
)
for module in modules
}
exported_symbols_by_module = {
pathlib.Path(blob).name: symbol_extraction.extract_exported_symbols(
blob
)
for blob in [vmlinux] + modules
}
# Create a dependency graph.
create_graph(
undefined_symbols_by_module, exported_symbols_by_module, args.output
)
if __name__ == "__main__":
sys.exit(main())