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