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