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