-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a simple script for mapping linker dependencies
This only supports FreeBSD currently. The graphviz portion is split from the linker dependency enumeration portion because the host where the linker dependencies are enumerated may not have easy access to graphviz and its associated libraries.
- Loading branch information
Showing
6 changed files
with
159 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include graph_linker_dependencies/templates/*.pyt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import platform | ||
import sys | ||
from setuptools import find_packages | ||
from setuptools import setup | ||
|
||
|
||
osname = platform.system().lower() | ||
if osname not in "freebsd": | ||
sys.exit("This package only works on FreeBSD.") | ||
|
||
GLD = "graph_linker_dependencies" | ||
|
||
setup( | ||
name="graph_linker_dependencies", | ||
version="0.1", | ||
description="Tool for graphing FreeBSD linker dependencies.", | ||
author="Enji Cooper", | ||
author_email="[email protected]", | ||
url="https://github.com/ngie-eign/scratch", | ||
include_package_data=True, | ||
packages=find_packages(where="src"), | ||
package_data={f"{GLD}": ["templates/*.pyt"]}, | ||
package_dir={"": "src"}, | ||
entry_points={ | ||
"console_scripts": [ | ||
f"create_link_dependency_graph={GLD}.create_link_dependency_graph:main" | ||
] | ||
}, | ||
requirements=["jinja2"], | ||
) |
2 changes: 2 additions & 0 deletions
2
tools/graph-linker-dependencies/src/graph_linker_dependencies/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import pkg_resources | ||
pkg_resources.declare_namespace(__name__) |
108 changes: 108 additions & 0 deletions
108
...s/graph-linker-dependencies/src/graph_linker_dependencies/create_link_dependency_graph.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
#!/usr/bin/env python | ||
|
||
import argparse | ||
import collections | ||
import os | ||
import pathlib | ||
|
||
# import pprint | ||
import pkg_resources | ||
import re | ||
import subprocess | ||
|
||
from jinja2 import Environment | ||
from jinja2 import FileSystemLoader | ||
from jinja2 import Template | ||
from jinja2 import select_autoescape | ||
|
||
|
||
LIBDEPS_CACHE = collections.defaultdict(list) | ||
LDCONFIG_HINT_RE = re.compile(r".+\s+=>\s+(.+)") | ||
NEEDED_SHLIB_RE = re.compile(r".+NEEDED\s+Shared library: \[(.+)\]") | ||
|
||
|
||
def build_library_path_cache(): | ||
ldconfig_hints_map = {} | ||
|
||
all_ldconfig_hints = subprocess.check_output(["ldconfig", "-r"], text=True) | ||
for ldconfig_hint in all_ldconfig_hints.splitlines(False): | ||
matches = LDCONFIG_HINT_RE.match(ldconfig_hint) | ||
if matches is None: | ||
continue | ||
|
||
so_full = so_split = matches.group(1) | ||
ldconfig_hints_map[so_full] = so_full | ||
|
||
while True: | ||
so_short = os.path.basename(so_split) | ||
ldconfig_hints_map[so_short] = ldconfig_hints_map[so_split] = so_full | ||
so_split, ext = os.path.splitext(so_split) | ||
if ext == ".so": | ||
break | ||
|
||
return ldconfig_hints_map | ||
|
||
|
||
def find_library_dependencies(library_, ldconfig_hints_map, libdep_cache): | ||
if library_ in libdep_cache: | ||
return | ||
|
||
lib_path = ldconfig_hints_map[library_] | ||
|
||
readelf_lines = subprocess.check_output(["readelf", "-d", lib_path], text=True) | ||
for readelf_line in readelf_lines.splitlines(False): | ||
matches = NEEDED_SHLIB_RE.match(readelf_line) | ||
if matches is None: | ||
continue | ||
libdep = matches.group(1) | ||
libdep_cache[library_].append(libdep) | ||
find_library_dependencies(libdep, ldconfig_hints_map, libdep_cache) | ||
|
||
|
||
def main(argv=None): | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument("library") | ||
parser.add_argument("--graph-generator-file") | ||
parser.add_argument("--graph-output-file") | ||
args = parser.parse_args(args=argv) | ||
|
||
libdep_cache = collections.defaultdict(list) | ||
|
||
ldconfig_hints_map = build_library_path_cache() | ||
|
||
library_full_path = str(pathlib.Path(args.library).resolve()) | ||
assert ( | ||
library_full_path in ldconfig_hints_map | ||
), f"{library_full_path} not found in ldconfig cache" | ||
|
||
find_library_dependencies(library_full_path, ldconfig_hints_map, libdep_cache) | ||
|
||
env = Environment( | ||
loader=FileSystemLoader( | ||
pkg_resources.resource_filename( | ||
"graph_linker_dependencies", | ||
"templates", | ||
) | ||
), | ||
autoescape=select_autoescape(), | ||
) | ||
template = env.get_template("libdep_template.pyt") | ||
|
||
library_name = pathlib.Path(args.library).stem | ||
graph_py_filename = ( | ||
args.graph_generator_file or f"graph_{library_name}_dependencies.py" | ||
) | ||
graph_output_file = ( | ||
args.graph_output_file or f"{library_name}_dependencies_graph.png" | ||
) | ||
with open(graph_py_filename, "w") as filep: | ||
filep.write( | ||
template.render( | ||
libdep_cache=libdep_cache, output_file=args.graph_output_file | ||
) | ||
) | ||
print(f"Please run {graph_py_filename} on host where python-graphviz is installed.") | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
2 changes: 2 additions & 0 deletions
2
tools/graph-linker-dependencies/src/graph_linker_dependencies/templates/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import pkg_resources | ||
pkg_resources.declare_namespace(__name__) |
14 changes: 14 additions & 0 deletions
14
tools/graph-linker-dependencies/src/graph_linker_dependencies/templates/libdep_template.pyt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
#!/usr/bin/env python | ||
|
||
try: | ||
import graphviz | ||
except ImportError: | ||
warnings.warn("This script requires python-graphviz") | ||
raise | ||
|
||
dot = graphviz.Digraph("libdependencies-graph", comment="Library Dependencies Graph") | ||
|
||
{% for library, dependencies in libdep_cache.items() %}dot.node("{{library}}") | ||
{% for dependency in dependencies %}dot.edge("{{library}}", "{{dependency}}") | ||
{% endfor %}{% endfor %} | ||
dot.render(format="png", outfile="{{output_file}}") |