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