#!/usr/bin/env python # ------------------------------------------------------------------------- # # Utility to dump PMD_INFO_STRING support from an object file # # ------------------------------------------------------------------------- import os import sys from optparse import OptionParser import string import json import platform # For running from development directory. It should take precedence over the # installed pyelftools. sys.path.insert(0, '.') from elftools import __version__ from elftools.common.exceptions import ELFError from elftools.common.py3compat import ( ifilter, byte2int, bytes2str, itervalues, str2bytes) from elftools.elf.elffile import ELFFile from elftools.elf.dynamic import DynamicSection, DynamicSegment from elftools.elf.enums import ENUM_D_TAG from elftools.elf.segments import InterpSegment from elftools.elf.sections import SymbolTableSection from elftools.elf.gnuversions import ( GNUVerSymSection, GNUVerDefSection, GNUVerNeedSection, ) from elftools.elf.relocation import RelocationSection from elftools.elf.descriptions import ( describe_ei_class, describe_ei_data, describe_ei_version, describe_ei_osabi, describe_e_type, describe_e_machine, describe_e_version_numeric, describe_p_type, describe_p_flags, describe_sh_type, describe_sh_flags, describe_symbol_type, describe_symbol_bind, describe_symbol_visibility, describe_symbol_shndx, describe_reloc_type, describe_dyn_tag, describe_ver_flags, ) from elftools.elf.constants import E_FLAGS from elftools.dwarf.dwarfinfo import DWARFInfo from elftools.dwarf.descriptions import ( describe_reg_name, describe_attr_value, set_global_machine_arch, describe_CFI_instructions, describe_CFI_register_rule, describe_CFI_CFA_rule, ) from elftools.dwarf.constants import ( DW_LNS_copy, DW_LNS_set_file, DW_LNE_define_file) from elftools.dwarf.callframe import CIE, FDE raw_output = False pcidb = None # =========================================== class Vendor: """ Class for vendors. This is the top level class for the devices belong to a specific vendor. self.devices is the device dictionary subdevices are in each device. """ def __init__(self, vendorStr): """ Class initializes with the raw line from pci.ids Parsing takes place inside __init__ """ self.ID = vendorStr.split()[0] self.name = vendorStr.replace("%s " % self.ID, "").rstrip() self.devices = {} def addDevice(self, deviceStr): """ Adds a device to self.devices takes the raw line from pci.ids """ s = deviceStr.strip() devID = s.split()[0] if devID in self.devices: pass else: self.devices[devID] = Device(deviceStr) def report(self): print self.ID, self.name for id, dev in self.devices.items(): dev.report() def find_device(self, devid): # convert to a hex string and remove 0x devid = hex(devid)[2:] try: return self.devices[devid] except: return Device("%s Unknown Device" % devid) class Device: def __init__(self, deviceStr): """ Class for each device. Each vendor has its own devices dictionary. """ s = deviceStr.strip() self.ID = s.split()[0] self.name = s.replace("%s " % self.ID, "") self.subdevices = {} def report(self): print "\t%s\t%s" % (self.ID, self.name) for subID, subdev in self.subdevices.items(): subdev.report() def addSubDevice(self, subDeviceStr): """ Adds a subvendor, subdevice to device. Uses raw line from pci.ids """ s = subDeviceStr.strip() spl = s.split() subVendorID = spl[0] subDeviceID = spl[1] subDeviceName = s.split(" ")[-1] devID = "%s:%s" % (subVendorID, subDeviceID) self.subdevices[devID] = SubDevice( subVendorID, subDeviceID, subDeviceName) def find_subid(self, subven, subdev): subven = hex(subven)[2:] subdev = hex(subdev)[2:] devid = "%s:%s" % (subven, subdev) try: return self.subdevices[devid] except: if (subven == "ffff" and subdev == "ffff"): return SubDevice("ffff", "ffff", "(All Subdevices)") else: return SubDevice(subven, subdev, "(Unknown Subdevice)") class SubDevice: """ Class for subdevices. """ def __init__(self, vendor, device, name): """ Class initializes with vendorid, deviceid and name """ self.vendorID = vendor self.deviceID = device self.name = name def report(self): print "\t\t%s\t%s\t%s" % (self.vendorID, self.deviceID, self.name) class PCIIds: """ Top class for all pci.ids entries. All queries will be asked to this class. PCIIds.vendors["0e11"].devices["0046"].\ subdevices["0e11:4091"].name = "Smart Array 6i" """ def __init__(self, filename): """ Prepares the directories. Checks local data file. Tries to load from local, if not found, downloads from web """ self.version = "" self.date = "" self.vendors = {} self.contents = None self.readLocal(filename) self.parse() def reportVendors(self): """Reports the vendors """ for vid, v in self.vendors.items(): print v.ID, v.name def report(self, vendor=None): """ Reports everything for all vendors or a specific vendor PCIIds.report() reports everything PCIIDs.report("0e11") reports only "Compaq Computer Corporation" """ if vendor is not None: self.vendors[vendor].report() else: for vID, v in self.vendors.items(): v.report() def find_vendor(self, vid): # convert vid to a hex string and remove the 0x vid = hex(vid)[2:] try: return self.vendors[vid] except: return Vendor("%s Unknown Vendor" % (vid)) def findDate(self, content): for l in content: if l.find("Date:") > -1: return l.split()[-2].replace("-", "") return None def parse(self): if len(self.contents) < 1: print "data/%s-pci.ids not found" % self.date else: vendorID = "" deviceID = "" for l in self.contents: if l[0] == "#": continue elif len(l.strip()) == 0: continue else: if l.find("\t\t") == 0: self.vendors[vendorID].devices[ deviceID].addSubDevice(l) elif l.find("\t") == 0: deviceID = l.strip().split()[0] self.vendors[vendorID].addDevice(l) else: vendorID = l.split()[0] self.vendors[vendorID] = Vendor(l) def readLocal(self, filename): """ Reads the local file """ self.contents = open(filename).readlines() self.date = self.findDate(self.contents) def loadLocal(self): """ Loads database from local. If there is no file, it creates a new one from web """ self.date = idsfile[0].split("/")[1].split("-")[0] self.readLocal() # ======================================= def search_file(filename, search_path): """ Given a search path, find file with requested name """ for path in string.split(search_path, ":"): candidate = os.path.join(path, filename) if os.path.exists(candidate): return os.path.abspath(candidate) return None class ReadElf(object): """ display_* methods are used to emit output into the output stream """ def __init__(self, file, output): """ file: stream object with the ELF file to read output: output stream to write to """ self.elffile = ELFFile(file) self.output = output # Lazily initialized if a debug dump is requested self._dwarfinfo = None self._versioninfo = None def _section_from_spec(self, spec): """ Retrieve a section given a "spec" (either number or name). Return None if no such section exists in the file. """ try: num = int(spec) if num < self.elffile.num_sections(): return self.elffile.get_section(num) else: return None except ValueError: # Not a number. Must be a name then return self.elffile.get_section_by_name(str2bytes(spec)) def pretty_print_pmdinfo(self, pmdinfo): global pcidb for i in pmdinfo["pci_ids"]: vendor = pcidb.find_vendor(i[0]) device = vendor.find_device(i[1]) subdev = device.find_subid(i[2], i[3]) print("%s (%s) : %s (%s) %s" % (vendor.name, vendor.ID, device.name, device.ID, subdev.name)) def parse_pmd_info_string(self, mystring): global raw_output global pcidb optional_pmd_info = [{'id': 'params', 'tag': 'PMD PARAMETERS'}] i = mystring.index("=") mystring = mystring[i + 2:] pmdinfo = json.loads(mystring) if raw_output: print(json.dumps(pmdinfo)) return print("PMD NAME: " + pmdinfo["name"]) for i in optional_pmd_info: try: print("%s: %s" % (i['tag'], pmdinfo[i['id']])) except KeyError as e: continue if (len(pmdinfo["pci_ids"]) != 0): print("PMD HW SUPPORT:") if pcidb is not None: self.pretty_print_pmdinfo(pmdinfo) else: print("VENDOR\t DEVICE\t SUBVENDOR\t SUBDEVICE") for i in pmdinfo["pci_ids"]: print("0x%04x\t 0x%04x\t 0x%04x\t\t 0x%04x" % (i[0], i[1], i[2], i[3])) print("") def display_pmd_info_strings(self, section_spec): """ Display a strings dump of a section. section_spec is either a section number or a name. """ section = self._section_from_spec(section_spec) if section is None: return data = section.data() dataptr = 0 while dataptr < len(data): while (dataptr < len(data) and not (32 <= byte2int(data[dataptr]) <= 127)): dataptr += 1 if dataptr >= len(data): break endptr = dataptr while endptr < len(data) and byte2int(data[endptr]) != 0: endptr += 1 mystring = bytes2str(data[dataptr:endptr]) rc = mystring.find("PMD_INFO_STRING") if (rc != -1): self.parse_pmd_info_string(mystring) dataptr = endptr def find_librte_eal(self, section): for tag in section.iter_tags(): if tag.entry.d_tag == 'DT_NEEDED': if "librte_eal" in tag.needed: return tag.needed return None def search_for_autoload_path(self): scanelf = self scanfile = None library = None section = self._section_from_spec(".dynamic") try: eallib = self.find_librte_eal(section) if eallib is not None: ldlibpath = os.environ.get('LD_LIBRARY_PATH') if ldlibpath is None: ldlibpath = "" dtr = self.get_dt_runpath(section) library = search_file(eallib, dtr + ":" + ldlibpath + ":/usr/lib64:/lib64:/usr/lib:/lib") if library is None: return (None, None) if raw_output is False: print("Scanning for autoload path in %s" % library) scanfile = open(library, 'rb') scanelf = ReadElf(scanfile, sys.stdout) except AttributeError: # Not a dynamic binary pass except ELFError: scanfile.close() return (None, None) section = scanelf._section_from_spec(".rodata") if section is None: if scanfile is not None: scanfile.close() return (None, None) data = section.data() dataptr = 0 while dataptr < len(data): while (dataptr < len(data) and not (32 <= byte2int(data[dataptr]) <= 127)): dataptr += 1 if dataptr >= len(data): break endptr = dataptr while endptr < len(data) and byte2int(data[endptr]) != 0: endptr += 1 mystring = bytes2str(data[dataptr:endptr]) rc = mystring.find("DPDK_PLUGIN_PATH") if (rc != -1): rc = mystring.find("=") return (mystring[rc + 1:], library) dataptr = endptr if scanfile is not None: scanfile.close() return (None, None) def get_dt_runpath(self, dynsec): for tag in dynsec.iter_tags(): if tag.entry.d_tag == 'DT_RUNPATH': return tag.runpath return "" def process_dt_needed_entries(self): """ Look to see if there are any DT_NEEDED entries in the binary And process those if there are """ global raw_output runpath = "" ldlibpath = os.environ.get('LD_LIBRARY_PATH') if ldlibpath is None: ldlibpath = "" dynsec = self._section_from_spec(".dynamic") try: runpath = self.get_dt_runpath(dynsec) except AttributeError: # dynsec is None, just return return for tag in dynsec.iter_tags(): if tag.entry.d_tag == 'DT_NEEDED': rc = tag.needed.find("librte_pmd") if (rc != -1): library = search_file(tag.needed, runpath + ":" + ldlibpath + ":/usr/lib64:/lib64:/usr/lib:/lib") if library is not None: if raw_output is False: print("Scanning %s for pmd information" % library) with open(library, 'rb') as file: try: libelf = ReadElf(file, sys.stdout) except ELFError as e: print("%s is no an ELF file" % library) continue libelf.process_dt_needed_entries() libelf.display_pmd_info_strings(".rodata") file.close() def scan_autoload_path(autoload_path): global raw_output if os.path.exists(autoload_path) is False: return try: dirs = os.listdir(autoload_path) except OSError as e: # Couldn't read the directory, give up return for d in dirs: dpath = os.path.join(autoload_path, d) if os.path.isdir(dpath): scan_autoload_path(dpath) if os.path.isfile(dpath): try: file = open(dpath, 'rb') readelf = ReadElf(file, sys.stdout) except ELFError as e: # this is likely not an elf file, skip it continue except IOError as e: # No permission to read the file, skip it continue if raw_output is False: print("Hw Support for library %s" % d) readelf.display_pmd_info_strings(".rodata") file.close() def scan_for_autoload_pmds(dpdk_path): """ search the specified application or path for a pmd autoload path then scan said path for pmds and report hw support """ global raw_output if (os.path.isfile(dpdk_path) is False): if raw_output is False: print("Must specify a file name") return file = open(dpdk_path, 'rb') try: readelf = ReadElf(file, sys.stdout) except ElfError as e: if raw_output is False: print("Unable to parse %s" % file) return (autoload_path, scannedfile) = readelf.search_for_autoload_path() if (autoload_path is None or autoload_path is ""): if (raw_output is False): print("No autoload path configured in %s" % dpdk_path) return if (raw_output is False): if (scannedfile is None): scannedfile = dpdk_path print("Found autoload path %s in %s" % (autoload_path, scannedfile)) file.close() if (raw_output is False): print("Discovered Autoload HW Support:") scan_autoload_path(autoload_path) return def main(stream=None): global raw_output global pcidb pcifile_default = "./pci.ids" # for unknown OS's assume local file if platform.system() == 'Linux': pcifile_default = "/usr/share/hwdata/pci.ids" elif platform.system() == 'FreeBSD': pcifile_default = "/usr/local/share/pciids/pci.ids" if not os.path.exists(pcifile_default): pcifile_default = "/usr/share/misc/pci_vendors" optparser = OptionParser( usage='usage: %prog [-hrtp] [-d ', description="Dump pmd hardware support info", add_help_option=True) optparser.add_option('-r', '--raw', action='store_true', dest='raw_output', help='Dump raw json strings') optparser.add_option("-d", "--pcidb", dest="pcifile", help="specify a pci database " "to get vendor names from", default=pcifile_default, metavar="FILE") optparser.add_option("-t", "--table", dest="tblout", help="output information on hw support as a hex table", action='store_true') optparser.add_option("-p", "--plugindir", dest="pdir", help="scan dpdk for autoload plugins", action='store_true') options, args = optparser.parse_args() if options.raw_output: raw_output = True if options.pcifile: pcidb = PCIIds(options.pcifile) if pcidb is None: print("Pci DB file not found") exit(1) if options.tblout: options.pcifile = None pcidb = None if (len(args) == 0): optparser.print_usage() exit(1) if options.pdir is True: exit(scan_for_autoload_pmds(args[0])) ldlibpath = os.environ.get('LD_LIBRARY_PATH') if (ldlibpath is None): ldlibpath = "" if (os.path.exists(args[0]) is True): myelffile = args[0] else: myelffile = search_file( args[0], ldlibpath + ":/usr/lib64:/lib64:/usr/lib:/lib") if (myelffile is None): print("File not found") sys.exit(1) with open(myelffile, 'rb') as file: try: readelf = ReadElf(file, sys.stdout) readelf.process_dt_needed_entries() readelf.display_pmd_info_strings(".rodata") sys.exit(0) except ELFError as ex: sys.stderr.write('ELF error: %s\n' % ex) sys.exit(1) # ------------------------------------------------------------------------- if __name__ == '__main__': main()