summaryrefslogtreecommitdiff
path: root/chromium/tools/linux
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/tools/linux
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'chromium/tools/linux')
-rwxr-xr-xchromium/tools/linux/dump-static-initializers.py231
1 files changed, 231 insertions, 0 deletions
diff --git a/chromium/tools/linux/dump-static-initializers.py b/chromium/tools/linux/dump-static-initializers.py
new file mode 100755
index 00000000000..865559b9320
--- /dev/null
+++ b/chromium/tools/linux/dump-static-initializers.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python
+# Copyright (c) 2012 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.
+
+"""Dump functions called by static intializers in a Linux Release binary.
+
+Usage example:
+ tools/linux/dump-static-intializers.py out/Release/chrome
+
+A brief overview of static initialization:
+1) the compiler writes out, per object file, a function that contains
+ the static intializers for that file.
+2) the compiler also writes out a pointer to that function in a special
+ section.
+3) at link time, the linker concatenates the function pointer sections
+ into a single list of all initializers.
+4) at run time, on startup the binary runs all function pointers.
+
+The functions in (1) all have mangled names of the form
+ _GLOBAL__I_foobar.cc
+using objdump, we can disassemble those functions and dump all symbols that
+they reference.
+"""
+
+import optparse
+import re
+import subprocess
+import sys
+
+# A map of symbol => informative text about it.
+NOTES = {
+ '__cxa_atexit@plt': 'registers a dtor to run at exit',
+ 'std::__ioinit': '#includes <iostream>, use <ostream> instead',
+}
+
+# Determine whether this is a git checkout (as opposed to e.g. svn).
+IS_GIT_WORKSPACE = (subprocess.Popen(
+ ['git', 'rev-parse'], stderr=subprocess.PIPE).wait() == 0)
+
+class Demangler(object):
+ """A wrapper around c++filt to provide a function to demangle symbols."""
+ def __init__(self):
+ self.cppfilt = subprocess.Popen(['c++filt'],
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+
+ def Demangle(self, sym):
+ """Given mangled symbol |sym|, return its demangled form."""
+ self.cppfilt.stdin.write(sym + '\n')
+ return self.cppfilt.stdout.readline().strip()
+
+# Matches for example: "cert_logger.pb.cc", capturing "cert_logger".
+protobuf_filename_re = re.compile(r'(.*)\.pb\.cc$')
+def QualifyFilenameAsProto(filename):
+ """Attempt to qualify a bare |filename| with a src-relative path, assuming it
+ is a protoc-generated file. If a single match is found, it is returned.
+ Otherwise the original filename is returned."""
+ if not IS_GIT_WORKSPACE:
+ return filename
+ match = protobuf_filename_re.match(filename)
+ if not match:
+ return filename
+ basename = match.groups(0)
+ gitlsfiles = subprocess.Popen(
+ ['git', 'ls-files', '--', '*/%s.proto' % basename],
+ stdout=subprocess.PIPE)
+ candidate = filename
+ for line in gitlsfiles.stdout:
+ if candidate != filename:
+ return filename # Multiple hits, can't help.
+ candidate = line.strip()
+ return candidate
+
+# Regex matching the substring of a symbol's demangled text representation most
+# likely to appear in a source file.
+# Example: "v8::internal::Builtins::InitBuiltinFunctionTable()" becomes
+# "InitBuiltinFunctionTable", since the first (optional & non-capturing) group
+# picks up any ::-qualification and the last fragment picks up a suffix that
+# starts with an opener.
+symbol_code_name_re = re.compile(r'^(?:[^(<[]*::)?([^:(<[]*).*?$')
+def QualifyFilename(filename, symbol):
+ """Given a bare filename and a symbol that occurs in it, attempt to qualify
+ it with a src-relative path. If more than one file matches, return the
+ original filename."""
+ if not IS_GIT_WORKSPACE:
+ return filename
+ match = symbol_code_name_re.match(symbol)
+ if not match:
+ return filename
+ symbol = match.group(1)
+ gitgrep = subprocess.Popen(
+ ['git', 'grep', '-l', symbol, '--', '*/%s' % filename],
+ stdout=subprocess.PIPE)
+ candidate = filename
+ for line in gitgrep.stdout:
+ if candidate != filename: # More than one candidate; return bare filename.
+ return filename
+ candidate = line.strip()
+ return candidate
+
+# Regex matching nm output for the symbols we're interested in.
+# See test_ParseNmLine for examples.
+nm_re = re.compile(r'(\S+) (\S+) t (?:_ZN12)?_GLOBAL__(?:sub_)?I_(.*)')
+def ParseNmLine(line):
+ """Given a line of nm output, parse static initializers as a
+ (file, start, size) tuple."""
+ match = nm_re.match(line)
+ if match:
+ addr, size, filename = match.groups()
+ return (filename, int(addr, 16), int(size, 16))
+
+
+def test_ParseNmLine():
+ """Verify the nm_re regex matches some sample lines."""
+ parse = ParseNmLine(
+ '0000000001919920 0000000000000008 t '
+ '_ZN12_GLOBAL__I_safe_browsing_service.cc')
+ assert parse == ('safe_browsing_service.cc', 26319136, 8), parse
+
+ parse = ParseNmLine(
+ '00000000026b9eb0 0000000000000024 t '
+ '_GLOBAL__sub_I_extension_specifics.pb.cc')
+ assert parse == ('extension_specifics.pb.cc', 40607408, 36), parse
+
+# Just always run the test; it is fast enough.
+test_ParseNmLine()
+
+
+def ParseNm(binary):
+ """Given a binary, yield static initializers as (file, start, size) tuples."""
+ nm = subprocess.Popen(['nm', '-S', binary], stdout=subprocess.PIPE)
+ for line in nm.stdout:
+ parse = ParseNmLine(line)
+ if parse:
+ yield parse
+
+# Regex matching objdump output for the symbols we're interested in.
+# Example line:
+# 12354ab: (disassembly, including <FunctionReference>)
+disassembly_re = re.compile(r'^\s+[0-9a-f]+:.*<(\S+)>')
+def ExtractSymbolReferences(binary, start, end):
+ """Given a span of addresses, returns symbol references from disassembly."""
+ cmd = ['objdump', binary, '--disassemble',
+ '--start-address=0x%x' % start, '--stop-address=0x%x' % end]
+ objdump = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+
+ refs = set()
+ for line in objdump.stdout:
+ if '__static_initialization_and_destruction' in line:
+ raise RuntimeError, ('code mentions '
+ '__static_initialization_and_destruction; '
+ 'did you accidentally run this on a Debug binary?')
+ match = disassembly_re.search(line)
+ if match:
+ (ref,) = match.groups()
+ if ref.startswith('.LC') or ref.startswith('_DYNAMIC'):
+ # Ignore these, they are uninformative.
+ continue
+ if ref.startswith('_GLOBAL__I_'):
+ # Probably a relative jump within this function.
+ continue
+ refs.add(ref)
+
+ return sorted(refs)
+
+def main():
+ parser = optparse.OptionParser(usage='%prog [option] filename')
+ parser.add_option('-d', '--diffable', dest='diffable',
+ action='store_true', default=False,
+ help='Prints the filename on each line, for more easily '
+ 'diff-able output. (Used by sizes.py)')
+ opts, args = parser.parse_args()
+ if len(args) != 1:
+ parser.error('missing filename argument')
+ return 1
+ binary = args[0]
+
+ demangler = Demangler()
+ file_count = 0
+ initializer_count = 0
+
+ files = ParseNm(binary)
+ if opts.diffable:
+ files = sorted(files)
+ for filename, addr, size in files:
+ file_count += 1
+ ref_output = []
+
+ qualified_filename = QualifyFilenameAsProto(filename)
+
+ if size == 2:
+ # gcc generates a two-byte 'repz retq' initializer when there is a
+ # ctor even when the ctor is empty. This is fixed in gcc 4.6, but
+ # Android uses gcc 4.4.
+ ref_output.append('[empty ctor, but it still has cost on gcc <4.6]')
+ else:
+ for ref in ExtractSymbolReferences(binary, addr, addr+size):
+ initializer_count += 1
+
+ ref = demangler.Demangle(ref)
+ if qualified_filename == filename:
+ qualified_filename = QualifyFilename(filename, ref)
+
+ note = ''
+ if ref in NOTES:
+ note = NOTES[ref]
+ elif ref.endswith('_2eproto()'):
+ note = 'protocol compiler bug: crbug.com/105626'
+
+ if note:
+ ref_output.append('%s [%s]' % (ref, note))
+ else:
+ ref_output.append(ref)
+
+ if opts.diffable:
+ print '\n'.join('# ' + qualified_filename + ' ' + r for r in ref_output)
+ else:
+ print '%s (initializer offset 0x%x size 0x%x)' % (qualified_filename,
+ addr, size)
+ print ''.join(' %s\n' % r for r in ref_output)
+
+ if opts.diffable:
+ print '#',
+ print 'Found %d static initializers in %d files.' % (initializer_count,
+ file_count)
+
+ return 0
+
+if '__main__' == __name__:
+ sys.exit(main())