13f6f8362SLouise Kilheeney#!/usr/bin/env python3 20a56e151SHemant Agrawal# SPDX-License-Identifier: BSD-3-Clause 30a56e151SHemant Agrawal# Copyright(c) 2016 Neil Horman <nhorman@tuxdriver.com> 40ce3cf4aSRobin Jarry# Copyright(c) 2022 Robin Jarry 50ce3cf4aSRobin Jarry# pylint: disable=invalid-name 6c6dab2a8SThomas Monjalon 70ce3cf4aSRobin Jarryr""" 80ce3cf4aSRobin JarryUtility to dump PMD_INFO_STRING support from DPDK binaries. 90ce3cf4aSRobin Jarry 100ce3cf4aSRobin JarryThis script prints JSON output to be interpreted by other tools. Here are some 110ce3cf4aSRobin Jarryexamples with jq: 120ce3cf4aSRobin Jarry 130ce3cf4aSRobin JarryGet the complete info for a given driver: 140ce3cf4aSRobin Jarry 150ce3cf4aSRobin Jarry %(prog)s dpdk-testpmd | \ 160ce3cf4aSRobin Jarry jq '.[] | select(.name == "cnxk_nix_inl")' 170ce3cf4aSRobin Jarry 180ce3cf4aSRobin JarryGet only the required kernel modules for a given driver: 190ce3cf4aSRobin Jarry 200ce3cf4aSRobin Jarry %(prog)s dpdk-testpmd | \ 210ce3cf4aSRobin Jarry jq '.[] | select(.name == "net_i40e").kmod' 220ce3cf4aSRobin Jarry 230ce3cf4aSRobin JarryGet only the required kernel modules for a given device: 240ce3cf4aSRobin Jarry 250ce3cf4aSRobin Jarry %(prog)s dpdk-testpmd | \ 26*e0a87c5fSRobin Jarry jq '.[] | select(.pci_ids[]? | .vendor == "15b3" and .device == "1013").kmod' 270ce3cf4aSRobin Jarry""" 280ce3cf4aSRobin Jarry 2981255f27SStephen Hemmingerimport argparse 300ce3cf4aSRobin Jarryimport json 310ce3cf4aSRobin Jarryimport logging 320ce3cf4aSRobin Jarryimport os 330ce3cf4aSRobin Jarryimport re 340ce3cf4aSRobin Jarryimport string 350ce3cf4aSRobin Jarryimport subprocess 360ce3cf4aSRobin Jarryimport sys 370ce3cf4aSRobin Jarryfrom pathlib import Path 380ce3cf4aSRobin Jarryfrom typing import Iterable, Iterator, List, Union 390ce3cf4aSRobin Jarry 400ce3cf4aSRobin Jarryimport elftools 410ce3cf4aSRobin Jarryfrom elftools.elf.elffile import ELFError, ELFFile 4281255f27SStephen Hemminger 43c6dab2a8SThomas Monjalon 440ce3cf4aSRobin Jarry# ---------------------------------------------------------------------------- 450ce3cf4aSRobin Jarrydef main() -> int: # pylint: disable=missing-docstring 460ce3cf4aSRobin Jarry try: 470ce3cf4aSRobin Jarry args = parse_args() 480ce3cf4aSRobin Jarry logging.basicConfig( 490ce3cf4aSRobin Jarry stream=sys.stderr, 500ce3cf4aSRobin Jarry format="%(levelname)s: %(message)s", 510ce3cf4aSRobin Jarry level={ 520ce3cf4aSRobin Jarry 0: logging.ERROR, 530ce3cf4aSRobin Jarry 1: logging.WARNING, 540ce3cf4aSRobin Jarry }.get(args.verbose, logging.DEBUG), 550ce3cf4aSRobin Jarry ) 560ce3cf4aSRobin Jarry info = parse_pmdinfo(args.elf_files, args.search_plugins) 570ce3cf4aSRobin Jarry print(json.dumps(info, indent=2)) 580ce3cf4aSRobin Jarry except BrokenPipeError: 59c6dab2a8SThomas Monjalon pass 600ce3cf4aSRobin Jarry except KeyboardInterrupt: 610ce3cf4aSRobin Jarry return 1 620ce3cf4aSRobin Jarry except Exception as e: # pylint: disable=broad-except 630ce3cf4aSRobin Jarry logging.error("%s", e) 640ce3cf4aSRobin Jarry return 1 650ce3cf4aSRobin Jarry return 0 66c6dab2a8SThomas Monjalon 67c6dab2a8SThomas Monjalon 680ce3cf4aSRobin Jarry# ---------------------------------------------------------------------------- 690ce3cf4aSRobin Jarrydef parse_args() -> argparse.Namespace: 70c6dab2a8SThomas Monjalon """ 710ce3cf4aSRobin Jarry Parse command line arguments. 72c6dab2a8SThomas Monjalon """ 7381255f27SStephen Hemminger parser = argparse.ArgumentParser( 740ce3cf4aSRobin Jarry description=__doc__, 750ce3cf4aSRobin Jarry formatter_class=argparse.RawDescriptionHelpFormatter, 760ce3cf4aSRobin Jarry ) 770ce3cf4aSRobin Jarry parser.add_argument( 780ce3cf4aSRobin Jarry "-p", 790ce3cf4aSRobin Jarry "--search-plugins", 800ce3cf4aSRobin Jarry action="store_true", 810ce3cf4aSRobin Jarry help=""" 820ce3cf4aSRobin Jarry In addition of ELF_FILEs and their linked dynamic libraries, also scan 830ce3cf4aSRobin Jarry the DPDK plugins path. 840ce3cf4aSRobin Jarry """, 850ce3cf4aSRobin Jarry ) 860ce3cf4aSRobin Jarry parser.add_argument( 870ce3cf4aSRobin Jarry "-v", 880ce3cf4aSRobin Jarry "--verbose", 890ce3cf4aSRobin Jarry action="count", 900ce3cf4aSRobin Jarry default=0, 910ce3cf4aSRobin Jarry help=""" 920ce3cf4aSRobin Jarry Display warnings due to linked libraries not found or ELF/JSON parsing 930ce3cf4aSRobin Jarry errors in these libraries. Use twice to show debug messages. 940ce3cf4aSRobin Jarry """, 950ce3cf4aSRobin Jarry ) 960ce3cf4aSRobin Jarry parser.add_argument( 970ce3cf4aSRobin Jarry "elf_files", 980ce3cf4aSRobin Jarry metavar="ELF_FILE", 990ce3cf4aSRobin Jarry nargs="+", 1000ce3cf4aSRobin Jarry type=existing_file, 1010ce3cf4aSRobin Jarry help=""" 1020ce3cf4aSRobin Jarry DPDK application binary or dynamic library. 1030ce3cf4aSRobin Jarry """, 1040ce3cf4aSRobin Jarry ) 1050ce3cf4aSRobin Jarry return parser.parse_args() 106c6dab2a8SThomas Monjalon 107c6dab2a8SThomas Monjalon 1080ce3cf4aSRobin Jarry# ---------------------------------------------------------------------------- 1090ce3cf4aSRobin Jarrydef parse_pmdinfo(paths: Iterable[Path], search_plugins: bool) -> List[dict]: 1100ce3cf4aSRobin Jarry """ 1110ce3cf4aSRobin Jarry Extract DPDK PMD info JSON strings from an ELF file. 11281255f27SStephen Hemminger 1130ce3cf4aSRobin Jarry :returns: 1140ce3cf4aSRobin Jarry A list of DPDK drivers info dictionaries. 1150ce3cf4aSRobin Jarry """ 1160ce3cf4aSRobin Jarry binaries = set(paths) 1170ce3cf4aSRobin Jarry for p in paths: 1180ce3cf4aSRobin Jarry binaries.update(get_needed_libs(p)) 1190ce3cf4aSRobin Jarry if search_plugins: 1200ce3cf4aSRobin Jarry # cast to list to avoid errors with update while iterating 1210ce3cf4aSRobin Jarry binaries.update(list(get_plugin_libs(binaries))) 122c6dab2a8SThomas Monjalon 1230ce3cf4aSRobin Jarry drivers = [] 124c6dab2a8SThomas Monjalon 1250ce3cf4aSRobin Jarry for b in binaries: 1260ce3cf4aSRobin Jarry logging.debug("analyzing %s", b) 127c6dab2a8SThomas Monjalon try: 1280ce3cf4aSRobin Jarry for s in get_elf_strings(b, ".rodata", "PMD_INFO_STRING="): 1290ce3cf4aSRobin Jarry try: 1300ce3cf4aSRobin Jarry info = json.loads(s) 1310ce3cf4aSRobin Jarry scrub_pci_ids(info) 1320ce3cf4aSRobin Jarry drivers.append(info) 1330ce3cf4aSRobin Jarry except ValueError as e: 1340ce3cf4aSRobin Jarry # invalid JSON, should never happen 1350ce3cf4aSRobin Jarry logging.warning("%s: %s", b, e) 1360ce3cf4aSRobin Jarry except ELFError as e: 1370ce3cf4aSRobin Jarry # only happens for discovered plugins that are not ELF 1380ce3cf4aSRobin Jarry logging.debug("%s: cannot parse ELF: %s", b, e) 139c6dab2a8SThomas Monjalon 1400ce3cf4aSRobin Jarry return drivers 141c6dab2a8SThomas Monjalon 142c6dab2a8SThomas Monjalon 1430ce3cf4aSRobin Jarry# ---------------------------------------------------------------------------- 1440ce3cf4aSRobin JarryPCI_FIELDS = ("vendor", "device", "subsystem_vendor", "subsystem_device") 1450ce3cf4aSRobin Jarry 1460ce3cf4aSRobin Jarry 1470ce3cf4aSRobin Jarrydef scrub_pci_ids(info: dict): 1480ce3cf4aSRobin Jarry """ 1490ce3cf4aSRobin Jarry Convert numerical ids to hex strings. 1500ce3cf4aSRobin Jarry Strip empty pci_ids lists. 1510ce3cf4aSRobin Jarry Strip wildcard 0xFFFF ids. 1520ce3cf4aSRobin Jarry """ 1530ce3cf4aSRobin Jarry pci_ids = [] 1540ce3cf4aSRobin Jarry for pci_fields in info.pop("pci_ids"): 1550ce3cf4aSRobin Jarry pci = {} 1560ce3cf4aSRobin Jarry for name, value in zip(PCI_FIELDS, pci_fields): 1570ce3cf4aSRobin Jarry if value != 0xFFFF: 1580ce3cf4aSRobin Jarry pci[name] = f"{value:04x}" 1590ce3cf4aSRobin Jarry if pci: 1600ce3cf4aSRobin Jarry pci_ids.append(pci) 1610ce3cf4aSRobin Jarry if pci_ids: 1620ce3cf4aSRobin Jarry info["pci_ids"] = pci_ids 1630ce3cf4aSRobin Jarry 1640ce3cf4aSRobin Jarry 1650ce3cf4aSRobin Jarry# ---------------------------------------------------------------------------- 1660ce3cf4aSRobin Jarrydef get_plugin_libs(binaries: Iterable[Path]) -> Iterator[Path]: 1670ce3cf4aSRobin Jarry """ 1680ce3cf4aSRobin Jarry Look into the provided binaries for DPDK_PLUGIN_PATH and scan the path 1690ce3cf4aSRobin Jarry for files. 1700ce3cf4aSRobin Jarry """ 1710ce3cf4aSRobin Jarry for b in binaries: 1720ce3cf4aSRobin Jarry for p in get_elf_strings(b, ".rodata", "DPDK_PLUGIN_PATH="): 1730ce3cf4aSRobin Jarry plugin_path = p.strip() 1740ce3cf4aSRobin Jarry logging.debug("discovering plugins in %s", plugin_path) 1750ce3cf4aSRobin Jarry for root, _, files in os.walk(plugin_path): 1760ce3cf4aSRobin Jarry for f in files: 1770ce3cf4aSRobin Jarry yield Path(root) / f 1780ce3cf4aSRobin Jarry # no need to search in other binaries. 1790ce3cf4aSRobin Jarry return 1800ce3cf4aSRobin Jarry 1810ce3cf4aSRobin Jarry 1820ce3cf4aSRobin Jarry# ---------------------------------------------------------------------------- 1830ce3cf4aSRobin Jarrydef existing_file(value: str) -> Path: 1840ce3cf4aSRobin Jarry """ 1850ce3cf4aSRobin Jarry Argparse type= callback to ensure an argument points to a valid file path. 1860ce3cf4aSRobin Jarry """ 1870ce3cf4aSRobin Jarry path = Path(value) 1880ce3cf4aSRobin Jarry if not path.is_file(): 1890ce3cf4aSRobin Jarry raise argparse.ArgumentTypeError(f"{value}: No such file") 1900ce3cf4aSRobin Jarry return path 1910ce3cf4aSRobin Jarry 1920ce3cf4aSRobin Jarry 1930ce3cf4aSRobin Jarry# ---------------------------------------------------------------------------- 1940ce3cf4aSRobin JarryPRINTABLE_BYTES = frozenset(string.printable.encode("ascii")) 1950ce3cf4aSRobin Jarry 1960ce3cf4aSRobin Jarry 1970ce3cf4aSRobin Jarrydef find_strings(buf: bytes, prefix: str) -> Iterator[str]: 1980ce3cf4aSRobin Jarry """ 1990ce3cf4aSRobin Jarry Extract strings of printable ASCII characters from a bytes buffer. 2000ce3cf4aSRobin Jarry """ 2010ce3cf4aSRobin Jarry view = memoryview(buf) 2020ce3cf4aSRobin Jarry start = None 2030ce3cf4aSRobin Jarry 2040ce3cf4aSRobin Jarry for i, b in enumerate(view): 2050ce3cf4aSRobin Jarry if start is None and b in PRINTABLE_BYTES: 2060ce3cf4aSRobin Jarry # mark beginning of string 2070ce3cf4aSRobin Jarry start = i 2080ce3cf4aSRobin Jarry continue 2090ce3cf4aSRobin Jarry if start is not None: 2100ce3cf4aSRobin Jarry if b in PRINTABLE_BYTES: 2110ce3cf4aSRobin Jarry # string not finished 2120ce3cf4aSRobin Jarry continue 2130ce3cf4aSRobin Jarry if b == 0: 2140ce3cf4aSRobin Jarry # end of string 2150ce3cf4aSRobin Jarry s = view[start:i].tobytes().decode("ascii") 2160ce3cf4aSRobin Jarry if s.startswith(prefix): 2170ce3cf4aSRobin Jarry yield s[len(prefix):] 2180ce3cf4aSRobin Jarry # There can be byte sequences where a non-printable byte 2190ce3cf4aSRobin Jarry # follows a printable one. Ignore that. 2200ce3cf4aSRobin Jarry start = None 2210ce3cf4aSRobin Jarry 2220ce3cf4aSRobin Jarry 2230ce3cf4aSRobin Jarry# ---------------------------------------------------------------------------- 2240ce3cf4aSRobin Jarrydef elftools_version(): 2250ce3cf4aSRobin Jarry """ 2260ce3cf4aSRobin Jarry Extract pyelftools version as a tuple of integers for easy comparison. 2270ce3cf4aSRobin Jarry """ 2280ce3cf4aSRobin Jarry version = getattr(elftools, "__version__", "") 2290ce3cf4aSRobin Jarry match = re.match(r"^(\d+)\.(\d+).*$", str(version)) 2300ce3cf4aSRobin Jarry if not match: 2310ce3cf4aSRobin Jarry # cannot determine version, hope for the best 2320ce3cf4aSRobin Jarry return (0, 24) 2330ce3cf4aSRobin Jarry return (int(match[1]), int(match[2])) 2340ce3cf4aSRobin Jarry 2350ce3cf4aSRobin Jarry 2360ce3cf4aSRobin JarryELFTOOLS_VERSION = elftools_version() 2370ce3cf4aSRobin Jarry 2380ce3cf4aSRobin Jarry 2390ce3cf4aSRobin Jarrydef from_elftools(s: Union[bytes, str]) -> str: 2400ce3cf4aSRobin Jarry """ 2410ce3cf4aSRobin Jarry Earlier versions of pyelftools (< 0.24) return bytes encoded with "latin-1" 2420ce3cf4aSRobin Jarry instead of python strings. 2430ce3cf4aSRobin Jarry """ 2440ce3cf4aSRobin Jarry if isinstance(s, bytes): 2450ce3cf4aSRobin Jarry return s.decode("latin-1") 2460ce3cf4aSRobin Jarry return s 2470ce3cf4aSRobin Jarry 2480ce3cf4aSRobin Jarry 2490ce3cf4aSRobin Jarrydef to_elftools(s: str) -> Union[bytes, str]: 2500ce3cf4aSRobin Jarry """ 2510ce3cf4aSRobin Jarry Earlier versions of pyelftools (< 0.24) assume that ELF section and tags 2520ce3cf4aSRobin Jarry are bytes encoded with "latin-1" instead of python strings. 2530ce3cf4aSRobin Jarry """ 2540ce3cf4aSRobin Jarry if ELFTOOLS_VERSION < (0, 24): 2550ce3cf4aSRobin Jarry return s.encode("latin-1") 2560ce3cf4aSRobin Jarry return s 2570ce3cf4aSRobin Jarry 2580ce3cf4aSRobin Jarry 2590ce3cf4aSRobin Jarry# ---------------------------------------------------------------------------- 2600ce3cf4aSRobin Jarrydef get_elf_strings(path: Path, section: str, prefix: str) -> Iterator[str]: 2610ce3cf4aSRobin Jarry """ 2620ce3cf4aSRobin Jarry Extract strings from a named ELF section in a file. 2630ce3cf4aSRobin Jarry """ 2640ce3cf4aSRobin Jarry with path.open("rb") as f: 2650ce3cf4aSRobin Jarry elf = ELFFile(f) 2660ce3cf4aSRobin Jarry sec = elf.get_section_by_name(to_elftools(section)) 2670ce3cf4aSRobin Jarry if not sec: 2680ce3cf4aSRobin Jarry return 2690ce3cf4aSRobin Jarry yield from find_strings(sec.data(), prefix) 2700ce3cf4aSRobin Jarry 2710ce3cf4aSRobin Jarry 2720ce3cf4aSRobin Jarry# ---------------------------------------------------------------------------- 2730ce3cf4aSRobin JarryLDD_LIB_RE = re.compile( 2740ce3cf4aSRobin Jarry r""" 2750ce3cf4aSRobin Jarry ^ # beginning of line 2760ce3cf4aSRobin Jarry \t # tab 2770ce3cf4aSRobin Jarry (\S+) # lib name 2780ce3cf4aSRobin Jarry \s+=>\s+ 2790ce3cf4aSRobin Jarry (/\S+) # lib path 2800ce3cf4aSRobin Jarry \s+ 2810ce3cf4aSRobin Jarry \(0x[0-9A-Fa-f]+\) # address 2820ce3cf4aSRobin Jarry \s* 2830ce3cf4aSRobin Jarry $ # end of line 2840ce3cf4aSRobin Jarry """, 2850ce3cf4aSRobin Jarry re.MULTILINE | re.VERBOSE, 2860ce3cf4aSRobin Jarry) 2870ce3cf4aSRobin Jarry 2880ce3cf4aSRobin Jarry 2890ce3cf4aSRobin Jarrydef get_needed_libs(path: Path) -> Iterator[Path]: 2900ce3cf4aSRobin Jarry """ 2910ce3cf4aSRobin Jarry Extract the dynamic library dependencies from an ELF executable. 2920ce3cf4aSRobin Jarry """ 2930ce3cf4aSRobin Jarry with subprocess.Popen( 2940ce3cf4aSRobin Jarry ["ldd", str(path)], stdout=subprocess.PIPE, stderr=subprocess.PIPE 2950ce3cf4aSRobin Jarry ) as proc: 2960ce3cf4aSRobin Jarry out, err = proc.communicate() 2970ce3cf4aSRobin Jarry if proc.returncode != 0: 2980ce3cf4aSRobin Jarry err = err.decode("utf-8").splitlines()[-1].strip() 2990ce3cf4aSRobin Jarry raise Exception(f"cannot read ELF file: {err}") 3000ce3cf4aSRobin Jarry for match in LDD_LIB_RE.finditer(out.decode("utf-8")): 3010ce3cf4aSRobin Jarry libname, libpath = match.groups() 3020ce3cf4aSRobin Jarry if libname.startswith("librte_"): 3030ce3cf4aSRobin Jarry libpath = Path(libpath) 3040ce3cf4aSRobin Jarry if libpath.is_file(): 3050ce3cf4aSRobin Jarry yield libpath.resolve() 3060ce3cf4aSRobin Jarry 3070ce3cf4aSRobin Jarry 3080ce3cf4aSRobin Jarry# ---------------------------------------------------------------------------- 3090ce3cf4aSRobin Jarryif __name__ == "__main__": 3100ce3cf4aSRobin Jarry sys.exit(main()) 311