tools: query binaries for HW and other support information

This tool searches for the primer sting PMD_DRIVER_INFO= in any ELF binary,
and, if found parses the remainder of the string as a json encoded string,
outputting the results in either a human readable or raw, script parseable
format

Note that, in the case of dynamically linked applications, pmdinfo.py will
scan for implicitly linked PMDs by searching the specified binaries
.dynamic section for DT_NEEDED entries that contain the substring
librte_pmd.  The DT_RUNPATH, LD_LIBRARY_PATH, /usr/lib and /lib are
searched for these libraries, in that order

If a file is specified with no path, it is assumed to be a PMD DSO, and the
LD_LIBRARY_PATH, /usr/lib[64]/ and /lib[64] is searched for it

Currently the tool can output data in 3 formats:

a) raw, suitable for scripting, where the raw JSON strings are dumped out
b) table format (default) where hex pci ids are dumped in a table format
c) pretty, where a user supplied pci.ids file is used to print out vendor
and device strings

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
Acked-by: Panu Matilainen <pmatilai@redhat.com>
Acked-by: Remy Horton <remy.horton@intel.com>
This commit is contained in:
Neil Horman 2016-06-17 14:46:24 -04:00 committed by Thomas Monjalon
parent 3d781ca328
commit c67c9a5c64
2 changed files with 631 additions and 0 deletions

View File

@ -128,6 +128,8 @@ install-runtime:
$(DESTDIR)$(sbindir)/dpdk_nic_bind)
$(Q)$(call rte_symlink, $(DESTDIR)$(bindir)/pmdinfogen, \
$(DESTDIR)$(bindir)/dpdk_pmdinfogen)
$(Q)$(call rte_symlink, $(DESTDIR)$(datadir)/tools/pmdinfo.py, \
$(DESTDIR)$(bindir)/dpdk_pmdinfo)
install-kmod:
ifneq ($(wildcard $O/kmod/*),)

629
tools/pmdinfo.py Executable file
View File

@ -0,0 +1,629 @@
#!/usr/bin/python
# -------------------------------------------------------------------------
# scripts/pmdinfo.py
#
# Utility to dump PMD_INFO_STRING support from an object file
#
# -------------------------------------------------------------------------
import os
import sys
from optparse import OptionParser
import string
import json
# 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(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
optparser = OptionParser(
usage='usage: %prog [-hrtp] [-d <pci id file] <elf-file>',
description="Dump pmd hardware support info",
add_help_option=True,
prog='pmdinfo.py')
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="/usr/share/hwdata/pci.ids", 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()