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