OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. |
| 4 |
| 5 """ |
| 6 Parse information about a PE file to summarize the on-disk and |
| 7 in-memory sizes of the sections, in decimal MB instead of in hex. This |
| 8 script will also automatically display diffs between two files if they |
| 9 have the same name. This script relies on having VS 2015 installed and is used |
| 10 to help investigate binary size regressions and improvements. |
| 11 |
| 12 Section information printed by dumpbin looks like this: |
| 13 |
| 14 SECTION HEADER #2 |
| 15 .rdata name |
| 16 5CCD56 virtual size |
| 17 1CEF000 virtual address (11CEF000 to 122BBD55) |
| 18 5CCE00 size of raw data |
| 19 1CEE000 file pointer to raw data (01CEE000 to 022BADFF) |
| 20 0 file pointer to relocation table |
| 21 0 file pointer to line numbers |
| 22 0 number of relocations |
| 23 0 number of line numbers |
| 24 40000040 flags |
| 25 Initialized Data |
| 26 Read Only |
| 27 |
| 28 The reports generated by this script look like this: |
| 29 |
| 30 > python tools\win\pe_summarize.py out\release\chrome.dll |
| 31 Size of out\release\chrome.dll is 41.190912 MB |
| 32 name: mem size , disk size |
| 33 .text: 33.199959 MB |
| 34 .rdata: 6.170416 MB |
| 35 .data: 0.713864 MB, 0.270336 MB |
| 36 .tls: 0.000025 MB |
| 37 CPADinfo: 0.000036 MB |
| 38 .rodata: 0.003216 MB |
| 39 .crthunk: 0.000064 MB |
| 40 .gfids: 0.001052 MB |
| 41 _RDATA: 0.000288 MB |
| 42 .rsrc: 0.130808 MB |
| 43 .reloc: 1.410172 MB |
| 44 |
| 45 Note that the .data section has separate in-memory and on-disk sizes due to |
| 46 zero-initialized data. Other sections have smaller discrepancies - the disk size |
| 47 is only printed if it differs from the memory size by more than 512 bytes. |
| 48 |
| 49 Note that many of the sections - such as .text, .rdata, and .rsrc - are shared |
| 50 between processes. Some sections - such as .reloc - are discarded after a |
| 51 process is loaded. Other sections, such as .data, produce private pages and are |
| 52 therefore objectively 'worse' than the others. |
| 53 """ |
| 54 |
| 55 import os |
| 56 import subprocess |
| 57 import sys |
| 58 |
| 59 |
| 60 def main(): |
| 61 if len(sys.argv) < 2: |
| 62 print r'Usage: %s PEFileName [OtherPeFileNames...]' % sys.argv[0] |
| 63 print r'Sample: %s chrome.dll' % sys.argv[0] |
| 64 print r'Sample: %s chrome.dll original\chrome.dll' % sys.argv[0] |
| 65 return 0 |
| 66 |
| 67 # Add to the path so that dumpbin can run. |
| 68 vs_dir = r'C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64' |
| 69 if not os.path.exists(os.path.join(vs_dir, 'dumpbin.exe')): |
| 70 print "Couldn't find dumpbin.exe. Visual Studio 2015 must be installed." |
| 71 return 0 |
| 72 os.environ['PATH'] = vs_dir + ';' + os.environ["PATH"] |
| 73 |
| 74 # Track the name of the last PE (Portable Executable) file to be processed - |
| 75 # file name only, without the path. |
| 76 last_pe_filepart = "" |
| 77 |
| 78 for pe_path in sys.argv[1:]: |
| 79 results = [] |
| 80 if not os.path.exists(pe_path): |
| 81 print '%s does not exist!' % pe_path |
| 82 continue |
| 83 |
| 84 print 'Size of %s is %1.6f MB' % (pe_path, os.path.getsize(pe_path) / 1e6) |
| 85 print '%10s: %9s , %9s' % ('name', 'mem size', 'disk size') |
| 86 |
| 87 sections = None |
| 88 command = 'dumpbin.exe /headers "%s"' % pe_path |
| 89 for line in subprocess.check_output(command).splitlines(): |
| 90 if line.startswith('SECTION HEADER #'): |
| 91 sections = [] |
| 92 elif type(sections) == type([]): |
| 93 # We must be processing a section header. |
| 94 sections.append(line.strip()) |
| 95 # When we've accumulated four lines of data, process them. |
| 96 if len(sections) == 4: |
| 97 name, memory_size, _, disk_size = sections |
| 98 assert name.count('name') == 1 |
| 99 assert memory_size.count('virtual size') == 1 |
| 100 assert disk_size.count('size of raw data') == 1 |
| 101 name = name.split()[0] |
| 102 memory_size = int(memory_size.split()[0], 16) |
| 103 disk_size = int(disk_size.split()[0], 16) |
| 104 # Print the sizes in decimal MB. This makes large numbers easier to |
| 105 # understand - 33.199959 is easier to read than 33199959. Decimal MB |
| 106 # is used to allow simple conversions to a precise number of bytes. |
| 107 if abs(memory_size - disk_size) < 512: |
| 108 print '%10s: %9.6f MB' % (name, memory_size / 1e6) |
| 109 else: |
| 110 print '%10s: %9.6f MB, %9.6f MB' % (name, memory_size / 1e6, |
| 111 disk_size / 1e6) |
| 112 results.append((name, memory_size)) |
| 113 sections = None |
| 114 |
| 115 print |
| 116 pe_filepart = os.path.split(pe_path)[1] |
| 117 if pe_filepart.lower() == last_pe_filepart.lower(): |
| 118 # Print out the section-by-section size changes, for memory sizes only. |
| 119 print 'Memory size change from %s to %s' % (last_pe_path, pe_path) |
| 120 total_delta = 0 |
| 121 for i in range(len(results)): |
| 122 # Make sure the current/last section names match or else the deltas |
| 123 # will be meaningless. Mismatches can occur when comparing 32-bit and |
| 124 # 64-bit binaries. |
| 125 if results[i][0] != last_results[i][0]: |
| 126 print "Names for section[%d] don't match. Aborting." % i |
| 127 return 0 |
| 128 delta = results[i][1] - last_results[i][1] |
| 129 total_delta += delta |
| 130 if delta: |
| 131 print '%12s: %7d bytes change' % (results[i][0], delta) |
| 132 print 'Total change: %7d bytes' % total_delta |
| 133 last_pe_filepart = pe_filepart |
| 134 last_pe_path = pe_path |
| 135 last_results = results |
| 136 |
| 137 |
| 138 if __name__ == '__main__': |
| 139 sys.exit(main()) |
OLD | NEW |