1#!/usr/bin/env python 2 3# ------------------------------------------------------------------------- 4# 5# Utility to dump PMD_INFO_STRING support from an object file 6# 7# ------------------------------------------------------------------------- 8from __future__ import print_function 9import json 10import os 11import platform 12import string 13import sys 14from elftools.common.exceptions import ELFError 15from elftools.common.py3compat import (byte2int, bytes2str, str2bytes) 16from elftools.elf.elffile import ELFFile 17from optparse import OptionParser 18 19# For running from development directory. It should take precedence over the 20# installed pyelftools. 21sys.path.insert(0, '.') 22 23raw_output = False 24pcidb = None 25 26# =========================================== 27 28 29class Vendor: 30 """ 31 Class for vendors. This is the top level class 32 for the devices belong to a specific vendor. 33 self.devices is the device dictionary 34 subdevices are in each device. 35 """ 36 37 def __init__(self, vendorStr): 38 """ 39 Class initializes with the raw line from pci.ids 40 Parsing takes place inside __init__ 41 """ 42 self.ID = vendorStr.split()[0] 43 self.name = vendorStr.replace("%s " % self.ID, "").rstrip() 44 self.devices = {} 45 46 def addDevice(self, deviceStr): 47 """ 48 Adds a device to self.devices 49 takes the raw line from pci.ids 50 """ 51 s = deviceStr.strip() 52 devID = s.split()[0] 53 if devID in self.devices: 54 pass 55 else: 56 self.devices[devID] = Device(deviceStr) 57 58 def report(self): 59 print(self.ID, self.name) 60 for id, dev in self.devices.items(): 61 dev.report() 62 63 def find_device(self, devid): 64 # convert to a hex string and remove 0x 65 devid = hex(devid)[2:] 66 try: 67 return self.devices[devid] 68 except: 69 return Device("%s Unknown Device" % devid) 70 71 72class Device: 73 74 def __init__(self, deviceStr): 75 """ 76 Class for each device. 77 Each vendor has its own devices dictionary. 78 """ 79 s = deviceStr.strip() 80 self.ID = s.split()[0] 81 self.name = s.replace("%s " % self.ID, "") 82 self.subdevices = {} 83 84 def report(self): 85 print("\t%s\t%s" % (self.ID, self.name)) 86 for subID, subdev in self.subdevices.items(): 87 subdev.report() 88 89 def addSubDevice(self, subDeviceStr): 90 """ 91 Adds a subvendor, subdevice to device. 92 Uses raw line from pci.ids 93 """ 94 s = subDeviceStr.strip() 95 spl = s.split() 96 subVendorID = spl[0] 97 subDeviceID = spl[1] 98 subDeviceName = s.split(" ")[-1] 99 devID = "%s:%s" % (subVendorID, subDeviceID) 100 self.subdevices[devID] = SubDevice( 101 subVendorID, subDeviceID, subDeviceName) 102 103 def find_subid(self, subven, subdev): 104 subven = hex(subven)[2:] 105 subdev = hex(subdev)[2:] 106 devid = "%s:%s" % (subven, subdev) 107 108 try: 109 return self.subdevices[devid] 110 except: 111 if (subven == "ffff" and subdev == "ffff"): 112 return SubDevice("ffff", "ffff", "(All Subdevices)") 113 else: 114 return SubDevice(subven, subdev, "(Unknown Subdevice)") 115 116 117class SubDevice: 118 """ 119 Class for subdevices. 120 """ 121 122 def __init__(self, vendor, device, name): 123 """ 124 Class initializes with vendorid, deviceid and name 125 """ 126 self.vendorID = vendor 127 self.deviceID = device 128 self.name = name 129 130 def report(self): 131 print("\t\t%s\t%s\t%s" % (self.vendorID, self.deviceID, self.name)) 132 133 134class PCIIds: 135 """ 136 Top class for all pci.ids entries. 137 All queries will be asked to this class. 138 PCIIds.vendors["0e11"].devices["0046"].\ 139 subdevices["0e11:4091"].name = "Smart Array 6i" 140 """ 141 142 def __init__(self, filename): 143 """ 144 Prepares the directories. 145 Checks local data file. 146 Tries to load from local, if not found, downloads from web 147 """ 148 self.version = "" 149 self.date = "" 150 self.vendors = {} 151 self.contents = None 152 self.readLocal(filename) 153 self.parse() 154 155 def reportVendors(self): 156 """Reports the vendors 157 """ 158 for vid, v in self.vendors.items(): 159 print(v.ID, v.name) 160 161 def report(self, vendor=None): 162 """ 163 Reports everything for all vendors or a specific vendor 164 PCIIds.report() reports everything 165 PCIIDs.report("0e11") reports only "Compaq Computer Corporation" 166 """ 167 if vendor is not None: 168 self.vendors[vendor].report() 169 else: 170 for vID, v in self.vendors.items(): 171 v.report() 172 173 def find_vendor(self, vid): 174 # convert vid to a hex string and remove the 0x 175 vid = hex(vid)[2:] 176 177 try: 178 return self.vendors[vid] 179 except: 180 return Vendor("%s Unknown Vendor" % (vid)) 181 182 def findDate(self, content): 183 for l in content: 184 if l.find("Date:") > -1: 185 return l.split()[-2].replace("-", "") 186 return None 187 188 def parse(self): 189 if len(self.contents) < 1: 190 print("data/%s-pci.ids not found" % self.date) 191 else: 192 vendorID = "" 193 deviceID = "" 194 for l in self.contents: 195 if l[0] == "#": 196 continue 197 elif len(l.strip()) == 0: 198 continue 199 else: 200 if l.find("\t\t") == 0: 201 self.vendors[vendorID].devices[ 202 deviceID].addSubDevice(l) 203 elif l.find("\t") == 0: 204 deviceID = l.strip().split()[0] 205 self.vendors[vendorID].addDevice(l) 206 else: 207 vendorID = l.split()[0] 208 self.vendors[vendorID] = Vendor(l) 209 210 def readLocal(self, filename): 211 """ 212 Reads the local file 213 """ 214 self.contents = open(filename).readlines() 215 self.date = self.findDate(self.contents) 216 217 def loadLocal(self): 218 """ 219 Loads database from local. If there is no file, 220 it creates a new one from web 221 """ 222 self.date = idsfile[0].split("/")[1].split("-")[0] 223 self.readLocal() 224 225 226# ======================================= 227 228def search_file(filename, search_path): 229 """ Given a search path, find file with requested name """ 230 for path in string.split(search_path, ":"): 231 candidate = os.path.join(path, filename) 232 if os.path.exists(candidate): 233 return os.path.abspath(candidate) 234 return None 235 236 237class ReadElf(object): 238 """ display_* methods are used to emit output into the output stream 239 """ 240 241 def __init__(self, file, output): 242 """ file: 243 stream object with the ELF file to read 244 245 output: 246 output stream to write to 247 """ 248 self.elffile = ELFFile(file) 249 self.output = output 250 251 # Lazily initialized if a debug dump is requested 252 self._dwarfinfo = None 253 254 self._versioninfo = None 255 256 def _section_from_spec(self, spec): 257 """ Retrieve a section given a "spec" (either number or name). 258 Return None if no such section exists in the file. 259 """ 260 try: 261 num = int(spec) 262 if num < self.elffile.num_sections(): 263 return self.elffile.get_section(num) 264 else: 265 return None 266 except ValueError: 267 # Not a number. Must be a name then 268 return self.elffile.get_section_by_name(str2bytes(spec)) 269 270 def pretty_print_pmdinfo(self, pmdinfo): 271 global pcidb 272 273 for i in pmdinfo["pci_ids"]: 274 vendor = pcidb.find_vendor(i[0]) 275 device = vendor.find_device(i[1]) 276 subdev = device.find_subid(i[2], i[3]) 277 print("%s (%s) : %s (%s) %s" % 278 (vendor.name, vendor.ID, device.name, 279 device.ID, subdev.name)) 280 281 def parse_pmd_info_string(self, mystring): 282 global raw_output 283 global pcidb 284 285 optional_pmd_info = [ 286 {'id': 'params', 'tag': 'PMD PARAMETERS'}, 287 {'id': 'kmod', 'tag': 'PMD KMOD DEPENDENCIES'} 288 ] 289 290 i = mystring.index("=") 291 mystring = mystring[i + 2:] 292 pmdinfo = json.loads(mystring) 293 294 if raw_output: 295 print(json.dumps(pmdinfo)) 296 return 297 298 print("PMD NAME: " + pmdinfo["name"]) 299 for i in optional_pmd_info: 300 try: 301 print("%s: %s" % (i['tag'], pmdinfo[i['id']])) 302 except KeyError: 303 continue 304 305 if (len(pmdinfo["pci_ids"]) != 0): 306 print("PMD HW SUPPORT:") 307 if pcidb is not None: 308 self.pretty_print_pmdinfo(pmdinfo) 309 else: 310 print("VENDOR\t DEVICE\t SUBVENDOR\t SUBDEVICE") 311 for i in pmdinfo["pci_ids"]: 312 print("0x%04x\t 0x%04x\t 0x%04x\t\t 0x%04x" % 313 (i[0], i[1], i[2], i[3])) 314 315 print("") 316 317 def display_pmd_info_strings(self, section_spec): 318 """ Display a strings dump of a section. section_spec is either a 319 section number or a name. 320 """ 321 section = self._section_from_spec(section_spec) 322 if section is None: 323 return 324 325 data = section.data() 326 dataptr = 0 327 328 while dataptr < len(data): 329 while (dataptr < len(data) and 330 not (32 <= byte2int(data[dataptr]) <= 127)): 331 dataptr += 1 332 333 if dataptr >= len(data): 334 break 335 336 endptr = dataptr 337 while endptr < len(data) and byte2int(data[endptr]) != 0: 338 endptr += 1 339 340 mystring = bytes2str(data[dataptr:endptr]) 341 rc = mystring.find("PMD_INFO_STRING") 342 if (rc != -1): 343 self.parse_pmd_info_string(mystring) 344 345 dataptr = endptr 346 347 def find_librte_eal(self, section): 348 for tag in section.iter_tags(): 349 if tag.entry.d_tag == 'DT_NEEDED': 350 if "librte_eal" in tag.needed: 351 return tag.needed 352 return None 353 354 def search_for_autoload_path(self): 355 scanelf = self 356 scanfile = None 357 library = None 358 359 section = self._section_from_spec(".dynamic") 360 try: 361 eallib = self.find_librte_eal(section) 362 if eallib is not None: 363 ldlibpath = os.environ.get('LD_LIBRARY_PATH') 364 if ldlibpath is None: 365 ldlibpath = "" 366 dtr = self.get_dt_runpath(section) 367 library = search_file(eallib, 368 dtr + ":" + ldlibpath + 369 ":/usr/lib64:/lib64:/usr/lib:/lib") 370 if library is None: 371 return (None, None) 372 if raw_output is False: 373 print("Scanning for autoload path in %s" % library) 374 scanfile = open(library, 'rb') 375 scanelf = ReadElf(scanfile, sys.stdout) 376 except AttributeError: 377 # Not a dynamic binary 378 pass 379 except ELFError: 380 scanfile.close() 381 return (None, None) 382 383 section = scanelf._section_from_spec(".rodata") 384 if section is None: 385 if scanfile is not None: 386 scanfile.close() 387 return (None, None) 388 389 data = section.data() 390 dataptr = 0 391 392 while dataptr < len(data): 393 while (dataptr < len(data) and 394 not (32 <= byte2int(data[dataptr]) <= 127)): 395 dataptr += 1 396 397 if dataptr >= len(data): 398 break 399 400 endptr = dataptr 401 while endptr < len(data) and byte2int(data[endptr]) != 0: 402 endptr += 1 403 404 mystring = bytes2str(data[dataptr:endptr]) 405 rc = mystring.find("DPDK_PLUGIN_PATH") 406 if (rc != -1): 407 rc = mystring.find("=") 408 return (mystring[rc + 1:], library) 409 410 dataptr = endptr 411 if scanfile is not None: 412 scanfile.close() 413 return (None, None) 414 415 def get_dt_runpath(self, dynsec): 416 for tag in dynsec.iter_tags(): 417 if tag.entry.d_tag == 'DT_RUNPATH': 418 return tag.runpath 419 return "" 420 421 def process_dt_needed_entries(self): 422 """ Look to see if there are any DT_NEEDED entries in the binary 423 And process those if there are 424 """ 425 global raw_output 426 runpath = "" 427 ldlibpath = os.environ.get('LD_LIBRARY_PATH') 428 if ldlibpath is None: 429 ldlibpath = "" 430 431 dynsec = self._section_from_spec(".dynamic") 432 try: 433 runpath = self.get_dt_runpath(dynsec) 434 except AttributeError: 435 # dynsec is None, just return 436 return 437 438 for tag in dynsec.iter_tags(): 439 if tag.entry.d_tag == 'DT_NEEDED': 440 rc = tag.needed.find(b"librte_pmd") 441 if (rc != -1): 442 library = search_file(tag.needed, 443 runpath + ":" + ldlibpath + 444 ":/usr/lib64:/lib64:/usr/lib:/lib") 445 if library is not None: 446 if raw_output is False: 447 print("Scanning %s for pmd information" % library) 448 with open(library, 'rb') as file: 449 try: 450 libelf = ReadElf(file, sys.stdout) 451 except ELFError: 452 print("%s is no an ELF file" % library) 453 continue 454 libelf.process_dt_needed_entries() 455 libelf.display_pmd_info_strings(".rodata") 456 file.close() 457 458 459def scan_autoload_path(autoload_path): 460 global raw_output 461 462 if os.path.exists(autoload_path) is False: 463 return 464 465 try: 466 dirs = os.listdir(autoload_path) 467 except OSError: 468 # Couldn't read the directory, give up 469 return 470 471 for d in dirs: 472 dpath = os.path.join(autoload_path, d) 473 if os.path.isdir(dpath): 474 scan_autoload_path(dpath) 475 if os.path.isfile(dpath): 476 try: 477 file = open(dpath, 'rb') 478 readelf = ReadElf(file, sys.stdout) 479 except ELFError: 480 # this is likely not an elf file, skip it 481 continue 482 except IOError: 483 # No permission to read the file, skip it 484 continue 485 486 if raw_output is False: 487 print("Hw Support for library %s" % d) 488 readelf.display_pmd_info_strings(".rodata") 489 file.close() 490 491 492def scan_for_autoload_pmds(dpdk_path): 493 """ 494 search the specified application or path for a pmd autoload path 495 then scan said path for pmds and report hw support 496 """ 497 global raw_output 498 499 if (os.path.isfile(dpdk_path) is False): 500 if raw_output is False: 501 print("Must specify a file name") 502 return 503 504 file = open(dpdk_path, 'rb') 505 try: 506 readelf = ReadElf(file, sys.stdout) 507 except ElfError: 508 if raw_output is False: 509 print("Unable to parse %s" % file) 510 return 511 512 (autoload_path, scannedfile) = readelf.search_for_autoload_path() 513 if (autoload_path is None or autoload_path is ""): 514 if (raw_output is False): 515 print("No autoload path configured in %s" % dpdk_path) 516 return 517 if (raw_output is False): 518 if (scannedfile is None): 519 scannedfile = dpdk_path 520 print("Found autoload path %s in %s" % (autoload_path, scannedfile)) 521 522 file.close() 523 if (raw_output is False): 524 print("Discovered Autoload HW Support:") 525 scan_autoload_path(autoload_path) 526 return 527 528 529def main(stream=None): 530 global raw_output 531 global pcidb 532 533 pcifile_default = "./pci.ids" # For unknown OS's assume local file 534 if platform.system() == 'Linux': 535 pcifile_default = "/usr/share/hwdata/pci.ids" 536 elif platform.system() == 'FreeBSD': 537 pcifile_default = "/usr/local/share/pciids/pci.ids" 538 if not os.path.exists(pcifile_default): 539 pcifile_default = "/usr/share/misc/pci_vendors" 540 541 optparser = OptionParser( 542 usage='usage: %prog [-hrtp] [-d <pci id file] <elf-file>', 543 description="Dump pmd hardware support info", 544 add_help_option=True) 545 optparser.add_option('-r', '--raw', 546 action='store_true', dest='raw_output', 547 help='Dump raw json strings') 548 optparser.add_option("-d", "--pcidb", dest="pcifile", 549 help="specify a pci database " 550 "to get vendor names from", 551 default=pcifile_default, metavar="FILE") 552 optparser.add_option("-t", "--table", dest="tblout", 553 help="output information on hw support as a " 554 "hex table", 555 action='store_true') 556 optparser.add_option("-p", "--plugindir", dest="pdir", 557 help="scan dpdk for autoload plugins", 558 action='store_true') 559 560 options, args = optparser.parse_args() 561 562 if options.raw_output: 563 raw_output = True 564 565 if options.pcifile: 566 pcidb = PCIIds(options.pcifile) 567 if pcidb is None: 568 print("Pci DB file not found") 569 exit(1) 570 571 if options.tblout: 572 options.pcifile = None 573 pcidb = None 574 575 if (len(args) == 0): 576 optparser.print_usage() 577 exit(1) 578 579 if options.pdir is True: 580 exit(scan_for_autoload_pmds(args[0])) 581 582 ldlibpath = os.environ.get('LD_LIBRARY_PATH') 583 if (ldlibpath is None): 584 ldlibpath = "" 585 586 if (os.path.exists(args[0]) is True): 587 myelffile = args[0] 588 else: 589 myelffile = search_file( 590 args[0], ldlibpath + ":/usr/lib64:/lib64:/usr/lib:/lib") 591 592 if (myelffile is None): 593 print("File not found") 594 sys.exit(1) 595 596 with open(myelffile, 'rb') as file: 597 try: 598 readelf = ReadElf(file, sys.stdout) 599 readelf.process_dt_needed_entries() 600 readelf.display_pmd_info_strings(".rodata") 601 sys.exit(0) 602 603 except ELFError as ex: 604 sys.stderr.write('ELF error: %s\n' % ex) 605 sys.exit(1) 606 607 608# ------------------------------------------------------------------------- 609if __name__ == '__main__': 610 main() 611