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 runpath = "" 438 ldlibpath = os.environ.get('LD_LIBRARY_PATH') 439 if ldlibpath is None: 440 ldlibpath = "" 441 442 dynsec = self._section_from_spec(".dynamic") 443 try: 444 runpath = self.get_dt_runpath(dynsec) 445 except AttributeError: 446 # dynsec is None, just return 447 return 448 449 for tag in dynsec.iter_tags(): 450 # pyelftools may return byte-strings, force decode them 451 if force_unicode(tag.entry.d_tag) == 'DT_NEEDED': 452 if 'librte_' in force_unicode(tag.needed): 453 library = search_file(force_unicode(tag.needed), 454 runpath + ":" + ldlibpath + 455 ":/usr/lib64:/lib64:/usr/lib:/lib") 456 if library is not None: 457 with io.open(library, 'rb') as file: 458 try: 459 libelf = ReadElf(file, sys.stdout) 460 except ELFError: 461 print("%s is no an ELF file" % library) 462 continue 463 libelf.process_dt_needed_entries() 464 libelf.display_pmd_info_strings(".rodata") 465 file.close() 466 467 468# compat: remove force_unicode & force_bytes when pyelftools<=0.23 support is 469# dropped. 470def force_unicode(s): 471 if hasattr(s, 'decode') and callable(s.decode): 472 s = s.decode('latin-1') # same encoding used in pyelftools py3compat 473 return s 474 475 476def force_bytes(s): 477 if hasattr(s, 'encode') and callable(s.encode): 478 s = s.encode('latin-1') # same encoding used in pyelftools py3compat 479 return s 480 481 482def scan_autoload_path(autoload_path): 483 global raw_output 484 485 if os.path.exists(autoload_path) is False: 486 return 487 488 try: 489 dirs = os.listdir(autoload_path) 490 except OSError: 491 # Couldn't read the directory, give up 492 return 493 494 for d in dirs: 495 dpath = os.path.join(autoload_path, d) 496 if os.path.isdir(dpath): 497 scan_autoload_path(dpath) 498 if os.path.isfile(dpath): 499 try: 500 file = io.open(dpath, 'rb') 501 readelf = ReadElf(file, sys.stdout) 502 except ELFError: 503 # this is likely not an elf file, skip it 504 continue 505 except IOError: 506 # No permission to read the file, skip it 507 continue 508 509 if raw_output is False: 510 print("Hw Support for library %s" % d) 511 readelf.display_pmd_info_strings(".rodata") 512 file.close() 513 514 515def scan_for_autoload_pmds(dpdk_path): 516 """ 517 search the specified application or path for a pmd autoload path 518 then scan said path for pmds and report hw support 519 """ 520 global raw_output 521 522 if (os.path.isfile(dpdk_path) is False): 523 if raw_output is False: 524 print("Must specify a file name") 525 return 526 527 file = io.open(dpdk_path, 'rb') 528 try: 529 readelf = ReadElf(file, sys.stdout) 530 except ElfError: 531 if raw_output is False: 532 print("Unable to parse %s" % file) 533 return 534 535 (autoload_path, scannedfile) = readelf.search_for_autoload_path() 536 if not autoload_path: 537 if (raw_output is False): 538 print("No autoload path configured in %s" % dpdk_path) 539 return 540 if (raw_output is False): 541 if (scannedfile is None): 542 scannedfile = dpdk_path 543 print("Found autoload path %s in %s" % (autoload_path, scannedfile)) 544 545 file.close() 546 if (raw_output is False): 547 print("Discovered Autoload HW Support:") 548 scan_autoload_path(autoload_path) 549 return 550 551 552def main(stream=None): 553 global raw_output 554 global pcidb 555 556 pcifile_default = "./pci.ids" # For unknown OS's assume local file 557 if platform.system() == 'Linux': 558 # hwdata is the legacy location, misc is supported going forward 559 pcifile_default = "/usr/share/misc/pci.ids" 560 if not os.path.exists(pcifile_default): 561 pcifile_default = "/usr/share/hwdata/pci.ids" 562 elif platform.system() == 'FreeBSD': 563 pcifile_default = "/usr/local/share/pciids/pci.ids" 564 if not os.path.exists(pcifile_default): 565 pcifile_default = "/usr/share/misc/pci_vendors" 566 567 optparser = OptionParser( 568 usage='usage: %prog [-hrtp] [-d <pci id file] <elf-file>', 569 description="Dump pmd hardware support info", 570 add_help_option=True) 571 optparser.add_option('-r', '--raw', 572 action='store_true', dest='raw_output', 573 help='Dump raw json strings') 574 optparser.add_option("-d", "--pcidb", dest="pcifile", 575 help="specify a pci database " 576 "to get vendor names from", 577 default=pcifile_default, metavar="FILE") 578 optparser.add_option("-t", "--table", dest="tblout", 579 help="output information on hw support as a " 580 "hex table", 581 action='store_true') 582 optparser.add_option("-p", "--plugindir", dest="pdir", 583 help="scan dpdk for autoload plugins", 584 action='store_true') 585 586 options, args = optparser.parse_args() 587 588 if options.raw_output: 589 raw_output = True 590 591 if options.pcifile: 592 pcidb = PCIIds(options.pcifile) 593 if pcidb is None: 594 print("Pci DB file not found") 595 exit(1) 596 597 if options.tblout: 598 options.pcifile = None 599 pcidb = None 600 601 if (len(args) == 0): 602 optparser.print_usage() 603 exit(1) 604 605 if options.pdir is True: 606 exit(scan_for_autoload_pmds(args[0])) 607 608 ldlibpath = os.environ.get('LD_LIBRARY_PATH') 609 if (ldlibpath is None): 610 ldlibpath = "" 611 612 if (os.path.exists(args[0]) is True): 613 myelffile = args[0] 614 else: 615 myelffile = search_file( 616 args[0], ldlibpath + ":/usr/lib64:/lib64:/usr/lib:/lib") 617 618 if (myelffile is None): 619 print("File not found") 620 sys.exit(1) 621 622 with io.open(myelffile, 'rb') as file: 623 try: 624 readelf = ReadElf(file, sys.stdout) 625 readelf.process_dt_needed_entries() 626 readelf.display_pmd_info_strings(".rodata") 627 sys.exit(0) 628 629 except ELFError as ex: 630 sys.stderr.write('ELF error: %s\n' % ex) 631 sys.exit(1) 632 633 634# ------------------------------------------------------------------------- 635if __name__ == '__main__': 636 main() 637