Index: tools/win/pe_summarize.py
|
diff --git a/tools/win/pe_summarize.py b/tools/win/pe_summarize.py
|
new file mode 100644
|
index 0000000000000000000000000000000000000000..3fbb9207e49d29560f0acdc0d5c2c3ec27cf9185
|
--- /dev/null
|
+++ b/tools/win/pe_summarize.py
|
@@ -0,0 +1,139 @@
|
+# Copyright (c) 2016 The Chromium Authors. All rights reserved.
|
+# Use of this source code is governed by a BSD-style license that can be
|
+# found in the LICENSE file.
|
+
|
+"""
|
+Parse information about a PE file to summarize the on-disk and
|
+in-memory sizes of the sections, in decimal MB instead of in hex. This
|
+script will also automatically display diffs between two files if they
|
+have the same name. This script relies on having VS 2015 installed and is used
|
+to help investigate binary size regressions and improvements.
|
+
|
+Section information printed by dumpbin looks like this:
|
+
|
+SECTION HEADER #2
|
+ .rdata name
|
+ 5CCD56 virtual size
|
+ 1CEF000 virtual address (11CEF000 to 122BBD55)
|
+ 5CCE00 size of raw data
|
+ 1CEE000 file pointer to raw data (01CEE000 to 022BADFF)
|
+ 0 file pointer to relocation table
|
+ 0 file pointer to line numbers
|
+ 0 number of relocations
|
+ 0 number of line numbers
|
+40000040 flags
|
+ Initialized Data
|
+ Read Only
|
+
|
+The reports generated by this script look like this:
|
+
|
+> python tools\win\pe_summarize.py out\release\chrome.dll
|
+Size of out\release\chrome.dll is 41.190912 MB
|
+ name: mem size , disk size
|
+ .text: 33.199959 MB
|
+ .rdata: 6.170416 MB
|
+ .data: 0.713864 MB, 0.270336 MB
|
+ .tls: 0.000025 MB
|
+ CPADinfo: 0.000036 MB
|
+ .rodata: 0.003216 MB
|
+ .crthunk: 0.000064 MB
|
+ .gfids: 0.001052 MB
|
+ _RDATA: 0.000288 MB
|
+ .rsrc: 0.130808 MB
|
+ .reloc: 1.410172 MB
|
+
|
+Note that the .data section has separate in-memory and on-disk sizes due to
|
+zero-initialized data. Other sections have smaller discrepancies - the disk size
|
+is only printed if it differs from the memory size by more than 512 bytes.
|
+
|
+Note that many of the sections - such as .text, .rdata, and .rsrc - are shared
|
+between processes. Some sections - such as .reloc - are discarded after a
|
+process is loaded. Other sections, such as .data, produce private pages and are
|
+therefore objectively 'worse' than the others.
|
+"""
|
+
|
+import os
|
+import subprocess
|
+import sys
|
+
|
+
|
+def main():
|
+ if len(sys.argv) < 2:
|
+ print r'Usage: %s PEFileName [OtherPeFileNames...]' % sys.argv[0]
|
+ print r'Sample: %s chrome.dll' % sys.argv[0]
|
+ print r'Sample: %s chrome.dll original\chrome.dll' % sys.argv[0]
|
+ return 0
|
+
|
+ # Add to the path so that dumpbin can run.
|
+ vs_dir = r'C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64'
|
+ if not os.path.exists(os.path.join(vs_dir, 'dumpbin.exe')):
|
+ print "Couldn't find dumpbin.exe. Visual Studio 2015 must be installed."
|
+ return 0
|
+ os.environ['PATH'] = vs_dir + ';' + os.environ["PATH"]
|
+
|
+ # Track the name of the last PE (Portable Executable) file to be processed -
|
+ # file name only, without the path.
|
+ last_pe_filepart = ""
|
+
|
+ for pe_path in sys.argv[1:]:
|
+ results = []
|
+ if not os.path.exists(pe_path):
|
+ print '%s does not exist!' % pe_path
|
+ continue
|
+
|
+ print 'Size of %s is %1.6f MB' % (pe_path, os.path.getsize(pe_path) / 1e6)
|
+ print '%10s: %9s , %9s' % ('name', 'mem size', 'disk size')
|
+
|
+ sections = None
|
+ command = 'dumpbin.exe /headers "%s"' % pe_path
|
+ for line in subprocess.check_output(command).splitlines():
|
+ if line.startswith('SECTION HEADER #'):
|
+ sections = []
|
+ elif type(sections) == type([]):
|
+ # We must be processing a section header.
|
+ sections.append(line.strip())
|
+ # When we've accumulated four lines of data, process them.
|
+ if len(sections) == 4:
|
+ name, memory_size, _, disk_size = sections
|
+ assert name.count('name') == 1
|
+ assert memory_size.count('virtual size') == 1
|
+ assert disk_size.count('size of raw data') == 1
|
+ name = name.split()[0]
|
+ memory_size = int(memory_size.split()[0], 16)
|
+ disk_size = int(disk_size.split()[0], 16)
|
+ # Print the sizes in decimal MB. This makes large numbers easier to
|
+ # understand - 33.199959 is easier to read than 33199959. Decimal MB
|
+ # is used to allow simple conversions to a precise number of bytes.
|
+ if abs(memory_size - disk_size) < 512:
|
+ print '%10s: %9.6f MB' % (name, memory_size / 1e6)
|
+ else:
|
+ print '%10s: %9.6f MB, %9.6f MB' % (name, memory_size / 1e6,
|
+ disk_size / 1e6)
|
+ results.append((name, memory_size))
|
+ sections = None
|
+
|
+ print
|
+ pe_filepart = os.path.split(pe_path)[1]
|
+ if pe_filepart.lower() == last_pe_filepart.lower():
|
+ # Print out the section-by-section size changes, for memory sizes only.
|
+ print 'Memory size change from %s to %s' % (last_pe_path, pe_path)
|
+ total_delta = 0
|
+ for i in range(len(results)):
|
+ # Make sure the current/last section names match or else the deltas
|
+ # will be meaningless. Mismatches can occur when comparing 32-bit and
|
+ # 64-bit binaries.
|
+ if results[i][0] != last_results[i][0]:
|
+ print "Names for section[%d] don't match. Aborting." % i
|
+ return 0
|
+ delta = results[i][1] - last_results[i][1]
|
+ total_delta += delta
|
+ if delta:
|
+ print '%12s: %7d bytes change' % (results[i][0], delta)
|
+ print 'Total change: %7d bytes' % total_delta
|
+ last_pe_filepart = pe_filepart
|
+ last_pe_path = pe_path
|
+ last_results = results
|
+
|
+
|
+if __name__ == '__main__':
|
+ sys.exit(main())
|
|