1 #!/usr/bin/env python 2 3 import os 4 import re 5 import sys 6 7 def SplitSections(buffer): 8 """Spin through the input buffer looking for section header lines. 9 When found, the name of the section is extracted. The entire contents 10 of that section is added to a result hashmap with the section name 11 as the key""" 12 13 # Match lines like 14 # |section_name: 15 # capturing section_name 16 headerPattern = re.compile(r'^\s+\|([a-z _]+)\:$', re.MULTILINE) 17 18 sections = {} 19 start = 0 20 anchor = -1 21 sectionName = '' 22 23 while True: 24 # Look for a section header 25 result = headerPattern.search(buffer, start) 26 27 # If there are no more, add a section from the last header to EOF 28 if result is None: 29 if anchor is not -1: 30 sections[sectionName] = buffer[anchor] 31 return sections 32 33 # Add the lines from the last header, to this one to the sections 34 # map indexed by the section name 35 if anchor is not -1: 36 sections[sectionName] = buffer[anchor:result.start()] 37 38 sectionName = result.group(1) 39 start = result.end() 40 anchor = start 41 42 return sections 43 44 def FindMethods(section): 45 """Spin through the 'method code index' section and extract all 46 method signatures. When found, they are added to a result list.""" 47 48 # Match lines like: 49 # |[abcd] com/example/app/Class.method:(args)return 50 # capturing the method signature 51 methodPattern = re.compile(r'^\s+\|\[\w{4}\] (.*)$', re.MULTILINE) 52 53 start = 0 54 methods = [] 55 56 while True: 57 # Look for a method name 58 result = methodPattern.search(section, start) 59 60 if result is None: 61 return methods 62 63 # Add the captured signature to the method list 64 methods.append(result.group(1)) 65 start = result.end() 66 67 def CallsMethod(codes, method): 68 """Spin through all the input method signatures. For each one, return 69 whether or not there is method invokation line in the codes section that 70 lists the method as the target.""" 71 72 start = 0 73 74 while True: 75 # Find the next reference to the method signature 76 match = codes.find(method, start) 77 78 if match is -1: 79 break; 80 81 # Find the beginning of the line the method reference is on 82 startOfLine = codes.rfind("\n", 0, match) + 1 83 84 # If the word 'invoke' comes between the beginning of the line 85 # and the method reference, then it is a call to that method rather 86 # than the beginning of the code section for that method. 87 if codes.find("invoke", startOfLine, match) is not -1: 88 return True 89 90 start = match + len(method) 91 92 return False 93 94 95 96 def main(): 97 if len(sys.argv) is not 2 or not sys.argv[1].endswith(".jar"): 98 print "Usage:", sys.argv[0], "<filename.jar>" 99 sys.exit() 100 101 command = 'dx --dex --dump-width=1000 --dump-to=-"" "%s"' % sys.argv[1] 102 103 pipe = os.popen(command) 104 105 # Read the whole dump file into memory 106 data = pipe.read() 107 sections = SplitSections(data) 108 109 pipe.close() 110 del(data) 111 112 methods = FindMethods(sections['method code index']) 113 codes = sections['codes'] 114 del(sections) 115 116 print "Dead Methods:" 117 count = 0 118 119 for method in methods: 120 if not CallsMethod(codes, method): 121 print "\t", method 122 count += 1 123 124 if count is 0: 125 print "\tNone" 126 127 if __name__ == '__main__': 128 main() 129