]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | #!/usr/bin/env python |
2 | # ------------------------------------------------------------------------- | |
3 | # | |
4 | # Utility to dump PMD_INFO_STRING support from an object file | |
5 | # | |
6 | # ------------------------------------------------------------------------- | |
7 | import os | |
8 | import sys | |
9 | from optparse import OptionParser | |
10 | import string | |
11 | import json | |
12 | import platform | |
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(json.dumps(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 | pcifile_default = "./pci.ids" # for unknown OS's assume local file | |
561 | if platform.system() == 'Linux': | |
562 | pcifile_default = "/usr/share/hwdata/pci.ids" | |
563 | elif platform.system() == 'FreeBSD': | |
564 | pcifile_default = "/usr/local/share/pciids/pci.ids" | |
565 | if not os.path.exists(pcifile_default): | |
566 | pcifile_default = "/usr/share/misc/pci_vendors" | |
567 | ||
568 | optparser = OptionParser( | |
569 | usage='usage: %prog [-hrtp] [-d <pci id file] <elf-file>', | |
570 | description="Dump pmd hardware support info", | |
571 | add_help_option=True) | |
572 | optparser.add_option('-r', '--raw', | |
573 | action='store_true', dest='raw_output', | |
574 | help='Dump raw json strings') | |
575 | optparser.add_option("-d", "--pcidb", dest="pcifile", | |
576 | help="specify a pci database " | |
577 | "to get vendor names from", | |
578 | default=pcifile_default, metavar="FILE") | |
579 | optparser.add_option("-t", "--table", dest="tblout", | |
580 | help="output information on hw support as a 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 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 | # ------------------------------------------------------------------------- | |
635 | if __name__ == '__main__': | |
636 | main() |