729 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			729 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| # -*- coding: utf-8 -*-
 | |
| #
 | |
| # Copyright Grégory Soutadé
 | |
| 
 | |
| # This file is part of SOAdvancedDissector
 | |
| 
 | |
| # SOAdvancedDissector is free software: you can redistribute it and/or modify
 | |
| # it under the terms of the GNU General Public License as published by
 | |
| # the Free Software Foundation, either version 3 of the License, or
 | |
| # (at your option) any later version.
 | |
| #
 | |
| # SOAdvancedDissector is distributed in the hope that it will be useful,
 | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| # GNU General Public License for more details.
 | |
| #
 | |
| # You should have received a copy of the GNU General Public License
 | |
| # along with SOAdvancedDissector.  If not, see <http://www.gnu.org/licenses/>.
 | |
| #
 | |
| 
 | |
| import re
 | |
| import os
 | |
| import shutil
 | |
| import struct
 | |
| import sys
 | |
| import subprocess
 | |
| import argparse
 | |
| from objects import *
 | |
| from cppprototypeparser import CPPPrototypeParser
 | |
| from display import ProgressionDisplay
 | |
| 
 | |
| #
 | |
| # Regexp for readelf -sW|c++filt
 | |
| #
 | |
| #      6: 006f25d0    20 OBJECT  WEAK   DEFAULT   20 vtable for dpdoc::Annot
 | |
| num_re        = '(?P<num>[0-9]+\:)'
 | |
| address_re    = '(?P<address>[0-9a-f]+)'
 | |
| size_re       = '(?P<size>[0-9]+)'
 | |
| ustr_re       = '(?P<{}>[A-Z]+)'
 | |
| type_re       = ustr_re.format('type')
 | |
| link_re       = ustr_re.format('link')
 | |
| visibility_re = ustr_re.format('visibility')
 | |
| ndx_re        = '(?P<ndx>[0-9]+)'
 | |
| name_re       = '(?P<name>.*)'
 | |
| 
 | |
| readelf       = r'[ ]+{}[ ]+{}[ ]+{}[ ]+{}[ ]+{}[ ]+{}[ ]+{}[ ]+{}'
 | |
| readelf = readelf.format(num_re, address_re, size_re, type_re, link_re, visibility_re,ndx_re,name_re)
 | |
| readelf_re = re.compile(readelf)
 | |
| 
 | |
| #
 | |
| # Regexp for readelf --sections|c++filt
 | |
| #
 | |
| #  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
 | |
| #  [ 1] .interp           PROGBITS        00000154 000154 000019 00   A  0   0  1
 | |
| num_re        = '(?P<num>\[[ ]*[0-9]+\])'
 | |
| name_re       = '(?P<name>.+)'
 | |
| type_re       = ustr_re.format('type')
 | |
| address_re    = '(?P<address>[0-9a-f]+)'
 | |
| offset_re     = '(?P<offset>[0-9a-f]+)'
 | |
| size_re       = '(?P<size>[0-9a-f]+)'
 | |
| sections      = r'[ ]+{}[ ]+{}[ ]+{}[ ]+{}[ ]+{}[ ]+{}[ ]+.*'
 | |
| sections = sections.format(num_re, name_re, type_re, address_re, offset_re, size_re)
 | |
| sections_re = re.compile(sections)
 | |
| 
 | |
| #
 | |
| # Regexp for vtable-dumper --demangle|c++filt
 | |
| #
 | |
| # 0     0000000000000000
 | |
| # 4     006e9fd0 (& typeinfo for zip::EditableStream)
 | |
| # Inherit from dputils::GuardedStream
 | |
| # Inherit from dpio::StreamClient
 | |
| # 8     002a793d zip::EditableStream::~EditableStream()
 | |
| dynvtable_idx_re   = re.compile(r'(?P<index>[0-9]+)[ ]+(?P<vtable_index>[-]?0[x]?[0-9a-f]+)')
 | |
| dynvtable_entry_re = re.compile(r'(?P<index>[0-9]+)[ ]+(?P<address>[0-9a-f]+) (?P<name>.*)')
 | |
| dynvtable_inherit_re = re.compile(r'Inherit from (?P<inherit>.*)')
 | |
| 
 | |
| #
 | |
| # Global variables
 | |
| #
 | |
| global_namespace = Namespace('global')
 | |
| namespaces = {'global':global_namespace} # Root namespace with one (without namespace) 'global'
 | |
| matched_lines  = [] # Matched lines from readelf symbols output
 | |
| sections_lines = [] # Matched lines from readelf sections output
 | |
| sections_addr  = [] # Contains tuple (section_addr_start, section_addr_end, section_offset)
 | |
| address_size   = 4  # Target address size (32bits by default)
 | |
| classes_with_inheritance = []
 | |
| namespace_dependencies = {}
 | |
| 
 | |
| display = ProgressionDisplay()
 | |
| 
 | |
| def line_count(filename):
 | |
|     """Do 'wc -l <filename>'
 | |
| 
 | |
|     Returns
 | |
|     -------
 | |
|     int
 | |
|         Line count of filename
 | |
|     """
 | |
|     try:
 | |
|         return int(subprocess.check_output(['wc', '-l', filename]).split()[0])
 | |
|     except:
 | |
|         return 0
 | |
| 
 | |
| def findBinOffset(addr):
 | |
|     """Find offset into binary file from target address
 | |
|        Sections must have been extracted
 | |
| 
 | |
|     Parameters
 | |
|     ----------
 | |
|     addr : int
 | |
|         Target address
 | |
| 
 | |
|     Returns
 | |
|     -------
 | |
|     int
 | |
|         Offset or None if not found
 | |
|     """
 | |
|     for (start, end, offset) in sections_addr:
 | |
|         if addr >= start and addr <= end:
 | |
|             return (addr - start) + offset
 | |
|     return None
 | |
| 
 | |
| def funcnameFromProtype(fullname):
 | |
|     """Return function name (name + parameters)
 | |
|     from prototype (includes namespaces + base class)
 | |
| 
 | |
|     Parameters
 | |
|     ----------
 | |
|     fullname : str
 | |
|         Full function name (package::NCXStreamReceiver::totalLengthReady(unsigned int))
 | |
| 
 | |
|     Returns
 | |
|     -------
 | |
|     str
 | |
|         Function name + parameters
 | |
|     """
 | |
|     if fullname.startswith('typeinfo'):
 | |
|         return ('typeinfo()', '')
 | |
|     parser = CPPPrototypeParser(fullname)
 | |
| 
 | |
|     return (parser.funcname + parser.fullparameters, '::'.join(parser.namespaces))
 | |
| 
 | |
| def findObjectInCache(name):
 | |
|     """Find class object in namespaces
 | |
| 
 | |
|     Parameters
 | |
|     ----------
 | |
|     name : str
 | |
|         Full class name (package::NCXStreamReceiver)
 | |
| 
 | |
|     Returns
 | |
|     -------
 | |
|     obj
 | |
|         Found class or None
 | |
|     """
 | |
| 
 | |
|     parser = CPPPrototypeParser(name)
 | |
| 
 | |
|     if parser.is_function:
 | |
|         return global_namespace.child(parser.funcname)
 | |
| 
 | |
|     if not parser.namespaces:
 | |
|         return global_namespace.child(parser.classname)
 | |
| 
 | |
|     if not parser.namespaces[0] in namespaces.keys():
 | |
|         return None
 | |
|     
 | |
|     namespace = namespaces[parser.namespaces[0]]
 | |
| 
 | |
|     # Don't directly use find on root to avoid duplicate name in sub namespaces
 | |
|     # eg : ns0::ns1::ns2::ns0::ns3::func
 | |
|     
 | |
|     for targetNamespace in parser.namespaces[1:]:
 | |
|         namespace = namespace.find(targetNamespace)
 | |
|         if not namespace: return None
 | |
| 
 | |
|     # print('findObjectInCache({}) --> {}'.format(name, namespace.name))
 | |
|     return namespace.find(parser.classname)
 | |
| 
 | |
| def findObjectFromAddr(addr):
 | |
|     """Find object from address (in readelf output)
 | |
| 
 | |
|     Parameters
 | |
|     ----------
 | |
|     addr : int
 | |
|         Object address
 | |
| 
 | |
|     Returns
 | |
|     -------
 | |
|     obj
 | |
|         Matched object or None
 | |
|     """
 | |
|     for match in matched_lines:
 | |
|         if int(match.group('address'),16) == addr:
 | |
|             #print(match.groups())
 | |
|             return match
 | |
| 
 | |
|     return None
 | |
| 
 | |
| def createClass(fullname):
 | |
|     """Find class object in namespaces or create
 | |
|     it if it doesn't exists
 | |
| 
 | |
|     Parameters
 | |
|     ----------
 | |
|     name : str
 | |
|         Full class name (package::NCXStreamReceiver)
 | |
| 
 | |
|     Returns
 | |
|     -------
 | |
|     obj
 | |
|         Found class or created one
 | |
|     """
 | |
|     parser = CPPPrototypeParser(fullname)
 | |
|     class_ = Class(parser.classname, '::'.join(parser.namespaces))
 | |
|     
 | |
|     if not parser.namespaces:
 | |
|         global_namespace.addChild(class_)
 | |
|     else:
 | |
|         if not parser.namespaces[0] in namespaces.keys():
 | |
|             lastNamespace = Namespace(parser.namespaces[0])
 | |
|             namespaces[parser.namespaces[0]] = lastNamespace
 | |
|         else:
 | |
|             lastNamespace = namespaces[parser.namespaces[0]]
 | |
|         for name in parser.namespaces[1:]:
 | |
|             newNamespace = lastNamespace.child(name)
 | |
|             if not newNamespace:
 | |
|                 newNamespace = Namespace(name)
 | |
|                 lastNamespace.addChild(newNamespace)
 | |
|             lastNamespace = newNamespace
 | |
|         lastNamespace.addChild(class_)
 | |
|     return class_
 | |
| 
 | |
| 
 | |
| def parseDynamicVtable(filename):
 | |
|     """Parse dynamic vtable (vtable-dumper) file
 | |
|     and fix static vtable
 | |
| 
 | |
|     Parameters
 | |
|     ----------
 | |
|     filename : str
 | |
|         Filename to parse
 | |
|     """
 | |
| 
 | |
|     display.setTarget(' * Parse dynamic vtable', line_count(filename), 10)
 | |
|     curObj = None
 | |
|     with open(filename, 'r') as fd:
 | |
|         for line in fd.readlines():
 | |
|             display.progress(1)
 | |
|             # Empty line -> end of current class
 | |
|             line = line.strip()
 | |
|             if len(line) == 0:
 | |
|                 curObj = None
 | |
|                 continue
 | |
|             
 | |
|             if curObj is None:
 | |
|                 # New vtable
 | |
|                 if not line.startswith('Vtable for '): continue
 | |
|                 objname = line[len('Vtable for '):]
 | |
|                 curObj = findObjectInCache(objname)
 | |
|             else:
 | |
|                 # First, try object vtable entry
 | |
|                 match = dynvtable_entry_re.match(line)
 | |
|                 if match:
 | |
|                     idx = int(match.group('index'))
 | |
|                     func = None
 | |
|                     if match.group('name') == '__cxa_pure_virtual':
 | |
|                         func = Function('virtfunc{}()'.format(idx), 0, True, True)
 | |
|                     else:
 | |
|                         funcaddr = int(match.group('address'),16)
 | |
|                         match = findObjectFromAddr(funcaddr)
 | |
|                         funcname = 'unknown_virtfunc{}()'.format(idx)
 | |
|                         funcnamespaces = ''
 | |
|                         if match:
 | |
|                             (funcname, funcnamespaces) = funcnameFromProtype(match.group('name'))
 | |
|                         else:
 | |
|                             sys.stderr.write('Error dynvtable0, no match for {}\n'.format(hex(funcaddr)))
 | |
|                         func = Function(funcname, funcaddr, virtual=True, namespace=funcnamespaces)
 | |
|                     curObj.updateVirtualFunction(int(idx/address_size), func)
 | |
|                     continue
 | |
|                 # Index vtable entry
 | |
|                 match = dynvtable_idx_re.match(line)
 | |
|                 if match:
 | |
|                     funcaddr = int(match.group('vtable_index'),16)
 | |
|                     funcname = 'vtable_index{}'.format(-funcaddr)
 | |
|                     func = Function(funcname, funcaddr, True)
 | |
|                     curObj.updateVirtualFunction(int(int(match.group('index'))/address_size), func)
 | |
|                     continue
 | |
|                 # Inherit entry
 | |
|                 match = dynvtable_inherit_re.match(line)
 | |
|                 if match:
 | |
|                     basename = match.group('inherit')
 | |
|                     base = findObjectInCache(basename)
 | |
|                     if not base:
 | |
|                         base = createClass(basename)
 | |
|                     curObj.addBaseClass(base)
 | |
|                     classes_with_inheritance.append(curObj)
 | |
|                     continue
 | |
|                 
 | |
|                 sys.stderr.write('Error dynvtable, no match for {}\n'.format(line))
 | |
|                 sys.stderr.flush()
 | |
|     display.finish()
 | |
| 
 | |
| def fixupInheritance():
 | |
|     """Fix inheritance : each class reports implemented pure virtual function
 | |
|     into its base(s)
 | |
|     """
 | |
|     global classes_with_inheritance
 | |
|     display.setTarget(' * Fixup inheritance', len(classes_with_inheritance))
 | |
|     for class_ in classes_with_inheritance:
 | |
|         display.progress(1)
 | |
|         class_.fixupInheritance()
 | |
|     display.finish()
 | |
|     classes_with_inheritance = None # Release memory
 | |
|             
 | |
| def parseStaticVtable(binfile):
 | |
|     """Parse vtable vtable object into binary file
 | |
|     and create static vtable entries
 | |
| 
 | |
|     Parameters
 | |
|     ----------
 | |
|     binfile : str
 | |
|         Filename of binary file to inspect objects
 | |
|     """
 | |
| 
 | |
|     display.setTarget(' * Parse static vtable', len(matched_lines), 10)
 | |
|     with open(binfile, 'rb') as fd:
 | |
|         for match in matched_lines:
 | |
|             display.progress(1)
 | |
|             name = match.group('name')
 | |
|             if not name.startswith('vtable for'): continue
 | |
|             address = int(match.group('address'), 16)
 | |
|             # vtable for mtext::cts::ListOfGlyphRunsCTS
 | |
|             classname = name[len('vtable for '):]
 | |
|             class_ = findObjectInCache(classname)
 | |
|             if class_ is None:
 | |
|                 class_ = createClass(classname)
 | |
|             binaddr = findBinOffset(address)
 | |
|             fd.seek(binaddr)
 | |
|             nb_funcs = int(int(match.group('size'))/address_size)
 | |
|             #print('vtable {} {} funcs'.format(classname, nb_funcs))
 | |
|             fd.seek(binaddr)
 | |
|             for i in range(0, nb_funcs):
 | |
|                 funcaddr = 0
 | |
|                 if address_size == 4:
 | |
|                     funcaddr, = struct.unpack('<I', fd.read(address_size))
 | |
|                 elif address_size == 8:
 | |
|                     funcaddr, = struct.unpack('<Q', fd.read(address_size))
 | |
|                 func = None
 | |
|                 if funcaddr == 0:
 | |
|                     # Address == 0 --> pure virtual
 | |
|                     func = Function('virtfunc{}()'.format(i), 0, True, True)
 | |
|                 else:
 | |
|                     funcname = ''
 | |
|                     funcnamespaces = ''
 | |
|                     if funcaddr == 0 or hex(funcaddr).startswith('0xf'): # Negative address
 | |
|                         funcname = 'vtable_index{}'.format(-funcaddr)
 | |
|                     else:
 | |
|                         match = findObjectFromAddr(funcaddr)
 | |
|                         try:
 | |
|                             if not match:
 | |
|                                 sys.stderr.write('No func found at : {}\n'.format(hex(funcaddr)))
 | |
|                             (funcname, funcnamespaces) = funcnameFromProtype(match.group('name'))
 | |
|                         except:
 | |
|                             if match:
 | |
|                                 sys.stderr.write('FFP except : {}'.format(match.group('name')))
 | |
|                                 funcname = 'unknown_virtfunc{}()'.format(i)
 | |
|                     func = Function(funcname, funcaddr, virtual=True, namespace=funcnamespaces)
 | |
|                 try:
 | |
|                     # print('Add virt {}'.format(func))
 | |
|                     class_.addVirtualFunction(func)
 | |
|                 except:
 | |
|                     print(match.group('name'))
 | |
|                     print(class_)
 | |
|                     raise
 | |
|     display.finish()
 | |
| 
 | |
| def parseTypeinfo():
 | |
|     """Parse typeinfo objects in matched_lines
 | |
|     and create classes
 | |
|     """
 | |
|     typeinfos = []
 | |
|     for match in matched_lines:
 | |
|         name = match.group('name')
 | |
|         # "typeinfo for css::MediaParser"
 | |
|         if not name.startswith('typeinfo for'): continue
 | |
|         typeinfos.append(name[len('typeinfo for '):])
 | |
|         # print(name)
 | |
| 
 | |
|     # Sort array before creating class in order to have the right
 | |
|     # class hierarchy
 | |
|     for classname in sorted(typeinfos):
 | |
|         #print(classname)
 | |
|         createClass(classname)
 | |
|         
 | |
| def parseSymbolFile(filename):
 | |
|     """Parse readelf symbols output file
 | |
|     and fill matched_lines
 | |
| 
 | |
|     Parameters
 | |
|     ----------
 | |
|     filename : str
 | |
|         Filename to parse
 | |
|     """
 | |
| 
 | |
|     display.setTarget(' * Parse symbols file', line_count(filename), 10)
 | |
|     with open(filename, 'r') as fd:
 | |
|         for line in fd.readlines():
 | |
|             display.progress(1)
 | |
|             line = line.rstrip()
 | |
|             if not line: continue
 | |
|             match = readelf_re.match(line)
 | |
|             if not match: continue
 | |
|             if match.group('type') not in ('FUNC', 'OBJECT'):
 | |
|                 continue
 | |
|             if match.group('link') not in ('GLOBAL', 'WEAK'):
 | |
|                 continue
 | |
|             if match.group('visibility') not in ('DEFAULT'):
 | |
|                 continue
 | |
|             matched_lines.append(match)
 | |
|         if matched_lines:
 | |
|             address_size = int(len(matched_lines[0].group('address'))/2)
 | |
|     display.finish()
 | |
| 
 | |
| def parseSectionFile(filename):
 | |
|     """Parse readelf sections output file
 | |
|     and fill sections_lines and sections_addr
 | |
| 
 | |
|     Parameters
 | |
|     ----------
 | |
|     filename : str
 | |
|         Filename to parse
 | |
|     """
 | |
| 
 | |
|     # We assume there is about 20 sections
 | |
|     display.setTarget(' * Parse sections file (1/2)', line_count(filename))
 | |
|     with open(filename, 'r') as fd:
 | |
|         for line in fd.readlines():
 | |
|             display.progress(1)
 | |
|             line = line.rstrip()
 | |
|             if not line: continue
 | |
|             match = sections_re.match(line)
 | |
|             if not match: continue
 | |
|             sections_lines.append(match)
 | |
|     display.finish()
 | |
|     
 | |
|     display.setTarget(' * Parse sections file (2/2)', line_count(filename))
 | |
|     for match in sections_lines:
 | |
|         display.progress(1)
 | |
|         start = int(match.group('address'), 16)
 | |
|         size = int(match.group('size'), 16)
 | |
|         offset = int(match.group('offset'), 16)
 | |
|         sections_addr.append((start, start+size, offset))
 | |
| 
 | |
|     display.finish()
 | |
| 
 | |
| def addAllMembers():
 | |
|     """Add all other members that have not been computed
 | |
|     """
 | |
| 
 | |
|     display.setTarget(' * Add all members', len(matched_lines), 10)
 | |
|     for match in matched_lines:
 | |
|         display.progress(1)
 | |
|         virtual = False
 | |
|         funcaddr = int(match.group('address'),16)
 | |
|         name = match.group('name')
 | |
|         if name.startswith('typeinfo') or\
 | |
|            name.startswith('vtable'):
 | |
|             continue
 | |
|         if name.startswith('non-virtual thunk to '):
 | |
|             name = name[len('non-virtual thunk to '):]
 | |
|             virtual = True
 | |
| 
 | |
|         parser = CPPPrototypeParser(name)
 | |
|         class_ = None
 | |
|         obj = None
 | |
|         funcname = ''
 | |
|         classname = ''
 | |
|         if match.group('type') == 'FUNC':
 | |
|             classname = parser.fullclassname
 | |
|             # C functions doesn't have () in their name
 | |
|             if not '(' in name:
 | |
|                 obj = Function(name + '()', funcaddr)
 | |
|                 global_namespace.addChild(obj)
 | |
|                 continue
 | |
|             else:
 | |
|                 (funcname, funcnamespaces) = funcnameFromProtype(name)
 | |
|                 obj = Function(funcname, funcaddr, virtual=virtual, namespace=funcnamespaces)
 | |
|                 # No classname : add into global namespace
 | |
|                 if not classname:
 | |
|                     global_namespace.addChild(obj)
 | |
|                     continue
 | |
|                 class_ = findObjectInCache(classname)
 | |
|         else: # Object
 | |
|             if parser.funcname:
 | |
|                 obj = Attribute(parser.funcname, funcaddr)
 | |
|                 classname = parser.classname
 | |
|             elif parser.classname:
 | |
|                 obj = Attribute(parser.classname, funcaddr)
 | |
|                 # No namespaces : add into global namespace
 | |
|                 if not parser.namespaces:
 | |
|                     global_namespace.addChild(obj)
 | |
|                     continue
 | |
|                 classname = '::'.join(parser.namespaces)
 | |
|             class_ = findObjectInCache(parser.fullclassname)
 | |
| 
 | |
|         # Try to search in namespaces from C++ method
 | |
|         if not class_ and parser.namespaces:
 | |
|             class_ = findObjectInCache('::'.join(parser.namespaces))
 | |
| 
 | |
|         # Try to search in namespaces from C function
 | |
|         if not class_ and classname in namespaces.keys():
 | |
|             class_ = namespaces[classname]
 | |
|         
 | |
|         # If still not, it's a new class/function
 | |
|         if not class_:
 | |
|             if not classname:
 | |
|                 sys.stderr.write('AAM Err3 "{}" "{}"\n'.format(name, funcname))
 | |
|                 continue
 | |
|             else:
 | |
|                 class_ = createClass(classname)
 | |
| 
 | |
|         try:
 | |
|             # Could be class or namespace
 | |
|             class_.addChild(obj)
 | |
|         except:
 | |
|             sys.stderr.write('Not class {} {}\n'.format(name, class_.name))
 | |
|             sys.stderr.flush()
 | |
|             # raise
 | |
|     display.finish()
 | |
| 
 | |
| def fixBadClassAssertion():
 | |
|     """We could have consider obj from a class and created it,
 | |
|     but in fact, it just namespace
 | |
|     """
 | |
|     toDelete = []
 | |
|     toAdd = []
 | |
|     display.setTarget(' * Fix bad class assertion (1/2)', len(global_namespace.childs))
 | |
|     for obj in global_namespace.childs:
 | |
|         display.progress(1)
 | |
|         if type(obj) == Class and obj.looksLikeNamespace():
 | |
|             if obj.name in namespaces:
 | |
|                 newNamespace = namespaces[obj.name]
 | |
|             else:
 | |
|                 newNamespace = Namespace(obj.name)
 | |
|             newNamespace.fillFrom(obj)
 | |
|             toAdd.append(newNamespace)
 | |
|             toDelete.append(obj)
 | |
|     display.finish()
 | |
| 
 | |
|     display.setTarget(' * Fix bad class assertion (2/2)', len(toDelete)+len(toAdd))
 | |
|     for obj in toDelete:
 | |
|         display.progress(1)
 | |
|         global_namespace.removeChild(obj)
 | |
|         
 | |
|     for obj in toAdd:
 | |
|         display.progress(1)
 | |
|         global_namespace.addChild(obj)
 | |
|     display.finish()
 | |
|         
 | |
| def analyseDependencies():
 | |
|     """Find classes present in method parameters but 
 | |
|     not previously found
 | |
|     """
 | |
|     display.setTarget(' * Analyse dependencies (1/2)', len(namespaces.keys()))
 | |
|     allParams = []
 | |
|     for namespace in namespaces.values():
 | |
|         display.progress(1)
 | |
|         params = namespace.getDependencies()
 | |
|         for param in params:
 | |
|             allParams.append(param)
 | |
|     display.finish()
 | |
| 
 | |
|     if allParams:
 | |
|         allParams = list(set(allParams))
 | |
|     display.setTarget(' * Analyse dependencies (2/2)', len(allParams))
 | |
|     for param in allParams:
 | |
|         display.progress(1)
 | |
|         class_ = findObjectInCache(param)
 | |
|         if not class_:
 | |
|             createClass(param)
 | |
|     display.finish()
 | |
|     
 | |
| header_re = re.compile('[A-Za-z0-9_-]+')
 | |
| def headerNameToFilename(name):
 | |
|     """Transform namespace name into filename
 | |
|     Keep only characters from header_re and change name
 | |
|     in lower case
 | |
| 
 | |
|     Parameters
 | |
|     ----------
 | |
|     name : str
 | |
|         Name to transform
 | |
| 
 | |
|     Returns
 | |
|     -------
 | |
|     str
 | |
|         Computed name
 | |
|     """
 | |
|     if '::' in name:
 | |
|         parser = CPPPrototypeParser(name)
 | |
|         if not parser.namespaces:
 | |
|             return None
 | |
|         res = parser.namespaces[0].lower()
 | |
|     else:
 | |
|         res = name.lower()
 | |
|     if '.so' in res:
 | |
|         res = os.path.basename(res).split('.so')[0] # Remove .so* extension
 | |
|     res = ''.join([c for c in res if header_re.match(c)])
 | |
| 
 | |
|     return res
 | |
| 
 | |
| 
 | |
| def outputHeader(namespace, filename):
 | |
|     """Create header file from namespace description
 | |
| 
 | |
|     Parameters
 | |
|     ----------
 | |
|     namespace : str
 | |
|         Namespace name
 | |
| 
 | |
|     filename : str
 | |
|         Output filename
 | |
|     """
 | |
| 
 | |
|     dependecies = namespace.getDependencies()
 | |
|     # Remove standard dependencies
 | |
|     if 'std' in dependecies:
 | |
|         dependecies.remove('std')
 | |
|     elif '__gnu_cxx' in dependecies:
 | |
|         dependecies.remove('__gnu_cxx')
 | |
|     headers = []
 | |
|     
 | |
|     define = '_{}'.format(os.path.basename(filename).upper().replace('.', '_'))
 | |
|     with open(filename, 'w') as fd:
 | |
|         fd.write('/*\n')
 | |
|         fd.write('    File automatically generated by SOAdvancedDissector.py\n')
 | |
|         fd.write('    More information at http://indefero.soutade.fr/p/soadvanceddissector\n')
 | |
|         fd.write('*/\n\n')
 | |
|         
 | |
|         fd.write('#ifndef {}\n'.format(define))
 | |
|         fd.write('#define {}\n\n'.format(define))
 | |
| 
 | |
|         for dep in dependecies:
 | |
|             headername = headerNameToFilename(dep)
 | |
|             if headername and not headername in headers:
 | |
|                 fd.write('#include <{}.h>\n'.format(headername))
 | |
|                 # Filter multiple headers
 | |
|                 headers.append(headername)
 | |
| 
 | |
|         if dependecies:
 | |
|             fd.write('\n\n')
 | |
| 
 | |
|         fd.write('{}'.format(namespace))
 | |
| 
 | |
|         fd.write('#endif // {}'.format(define))
 | |
| 
 | |
| def writeResult(target, outputDir, cleanOutputDir):
 | |
|     """Write result into header files (one per namespace)
 | |
| 
 | |
|     Parameters
 | |
|     ----------
 | |
|     cleanOutputDir : bool
 | |
|         Clean output directory before processing
 | |
|     """
 | |
| 
 | |
|     if cleanOutputDir:
 | |
|         print('Clean {}'.format(outputDir))
 | |
|         shutil.rmtree(outputDir, ignore_errors=True)
 | |
| 
 | |
|     if not os.path.exists(outputDir):
 | |
|         os.mkdir(outputDir)
 | |
| 
 | |
|     setPrintIndent(True)
 | |
|     keys = namespaces.keys()
 | |
|     display.setTarget(' * Write output files', len(keys))   
 | |
|     for namespace in keys:
 | |
|         if namespace == 'std': continue # Don't try to write std classes
 | |
|         filename = '{}.h'.format(headerNameToFilename(namespace))
 | |
|         if namespace == 'global':
 | |
|             filename = '{}.h'.format(headerNameToFilename(target))
 | |
|         outputHeader(namespaces[namespace], '{}/{}'.format(outputDir, filename))
 | |
|         display.progress(1)
 | |
|     display.finish()
 | |
|     setPrintIndent(False)
 | |
|     
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     parser = argparse.ArgumentParser(description='Extract interfaces (classes, functions, variables) from a GNU/Linux shared library')
 | |
|     parser.add_argument('-f', '--file', help="Target file",
 | |
|                       dest="target", required=True)
 | |
|     parser.add_argument('-s', '--section-file', help="Section file (result from 'readelf --sections|c++filt')",
 | |
|                       dest="section_file", required=True)
 | |
|     parser.add_argument('-S', '--symbol-file', help="Symbol file (result from 'readelf -sW|c++filt')",
 | |
|                       dest="symbol_file", required=True)
 | |
|     parser.add_argument('-V', '--vtable-file', help="Dynamic vtable file (result from 'vtable-dumper --demangle|c++filt')",
 | |
|                       dest="vtable_file")
 | |
|     parser.add_argument('-o', '--output-dir', help="output directory (default ./output)",
 | |
|                       default="./output", dest="output_dir")
 | |
|     parser.add_argument('-c', '--clean-output-dir', help="Clean output directory before computing (instead update it)",
 | |
|                         default=False, action="store_true", dest="clean_output")
 | |
|     parser.add_argument('-r', '--print-raw-virtual-table', help="Print raw virtual table (debug purpose)",
 | |
|                         default=False, action="store_true", dest="raw_virtual_table")
 | |
|     args = parser.parse_args()
 | |
|     
 | |
|     setPrintRawVirtualTable(args.raw_virtual_table)
 | |
|     
 | |
|     print('Analyse {}'.format(args.target))
 | |
| 
 | |
|     parseSectionFile(args.section_file)
 | |
|     parseSymbolFile(args.symbol_file)
 | |
|     parseTypeinfo()
 | |
|     parseStaticVtable(args.target)
 | |
| 
 | |
|     if args.vtable_file:
 | |
|         parseDynamicVtable(args.vtable_file)
 | |
|         fixupInheritance()
 | |
|         
 | |
|     addAllMembers()
 | |
|     
 | |
|     if args.vtable_file:
 | |
|         fixBadClassAssertion()
 | |
| 
 | |
|     analyseDependencies()
 | |
|     writeResult(args.target, args.output_dir, args.clean_output)
 | |
| 
 | |
|     print('Result wrote in {}'.format(args.output_dir))
 |