Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / components / breakpad / tools / generate_breakpad_symbols.py
1 #!/usr/bin/env python
2 # Copyright 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """A tool to generate symbols for a binary suitable for breakpad.
7
8 Currently, the tool only supports Linux, Android, and Mac. Support for other
9 platforms is planned.
10 """
11
12 import errno
13 import optparse
14 import os
15 import Queue
16 import re
17 import shutil
18 import subprocess
19 import sys
20 import threading
21
22
23 CONCURRENT_TASKS=4
24
25
26 def GetCommandOutput(command):
27   """Runs the command list, returning its output.
28
29   Prints the given command (which should be a list of one or more strings),
30   then runs it and returns its output (stdout) as a string.
31
32   From chromium_utils.
33   """
34   devnull = open(os.devnull, 'w')
35   proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=devnull,
36                           bufsize=1)
37   output = proc.communicate()[0]
38   return output
39
40
41 def GetDumpSymsBinary(build_dir=None):
42   """Returns the path to the dump_syms binary."""
43   DUMP_SYMS = 'dump_syms'
44   dump_syms_bin = os.path.join(os.path.expanduser(build_dir), DUMP_SYMS)
45   if not os.access(dump_syms_bin, os.X_OK):
46     print 'Cannot find %s.' % DUMP_SYMS
47     sys.exit(1)
48
49   return dump_syms_bin
50
51
52 def Resolve(path, exe_path, loader_path, rpaths):
53   """Resolve a dyld path.
54
55   @executable_path is replaced with |exe_path|
56   @loader_path is replaced with |loader_path|
57   @rpath is replaced with the first path in |rpaths| where the referenced file
58       is found
59   """
60   path = path.replace('@loader_path', loader_path)
61   path = path.replace('@executable_path', exe_path)
62   if path.find('@rpath') != -1:
63     for rpath in rpaths:
64       new_path = Resolve(path.replace('@rpath', rpath), exe_path, loader_path,
65                          [])
66       if os.access(new_path, os.X_OK):
67         return new_path
68     return ''
69   return path
70
71
72 def GetSharedLibraryDependenciesLinux(binary):
73   """Return absolute paths to all shared library dependecies of the binary.
74
75   This implementation assumes that we're running on a Linux system."""
76   ldd = GetCommandOutput(['ldd', binary])
77   lib_re = re.compile('\t.* => (.+) \(.*\)$')
78   result = []
79   for line in ldd.splitlines():
80     m = lib_re.match(line)
81     if m:
82       result.append(m.group(1))
83   return result
84
85
86 def GetSharedLibraryDependenciesMac(binary, exe_path):
87   """Return absolute paths to all shared library dependecies of the binary.
88
89   This implementation assumes that we're running on a Mac system."""
90   loader_path = os.path.dirname(binary)
91   otool = GetCommandOutput(['otool', '-l', binary]).splitlines()
92   rpaths = []
93   for idx, line in enumerate(otool):
94     if line.find('cmd LC_RPATH') != -1:
95       m = re.match(' *path (.*) \(offset .*\)$', otool[idx+2])
96       rpaths.append(m.group(1))
97
98   otool = GetCommandOutput(['otool', '-L', binary]).splitlines()
99   lib_re = re.compile('\t(.*) \(compatibility .*\)$')
100   deps = []
101   for line in otool:
102     m = lib_re.match(line)
103     if m:
104       dep = Resolve(m.group(1), exe_path, loader_path, rpaths)
105       if dep:
106         deps.append(os.path.normpath(dep))
107   return deps
108
109
110 def GetSharedLibraryDependencies(options, binary, exe_path):
111   """Return absolute paths to all shared library dependecies of the binary."""
112   deps = []
113   if sys.platform.startswith('linux'):
114     deps = GetSharedLibraryDependenciesLinux(binary)
115   elif sys.platform == 'darwin':
116     deps = GetSharedLibraryDependenciesMac(binary, exe_path)
117   else:
118     print "Platform not supported."
119     sys.exit(1)
120
121   result = []
122   build_dir = os.path.abspath(options.build_dir)
123   for dep in deps:
124     if (os.access(dep, os.X_OK) and
125         os.path.abspath(os.path.dirname(dep)).startswith(build_dir)):
126       result.append(dep)
127   return result
128
129
130 def mkdir_p(path):
131   """Simulates mkdir -p."""
132   try:
133     os.makedirs(path)
134   except OSError as e:
135     if e.errno == errno.EEXIST and os.path.isdir(path):
136       pass
137     else: raise
138
139
140 def GenerateSymbols(options, binaries):
141   """Dumps the symbols of binary and places them in the given directory."""
142
143   queue = Queue.Queue()
144   print_lock = threading.Lock()
145
146   def _Worker():
147     while True:
148       binary = queue.get()
149
150       should_dump_syms = True
151       reason = "no reason"
152
153       output_path = os.path.join(
154           options.symbols_dir, os.path.basename(binary))
155       if os.path.isdir(output_path):
156         if os.path.getmtime(binary) < os.path.getmtime(output_path):
157           should_dump_syms = False
158           reason = "symbols are more current than binary"
159
160       if not should_dump_syms:
161         if options.verbose:
162           with print_lock:
163             print "Skipping %s (%s)" % (binary, reason)
164         queue.task_done()
165         continue
166
167       if options.verbose:
168         with print_lock:
169           print "Generating symbols for %s" % binary
170
171       if os.path.isdir(output_path):
172         os.utime(output_path, None)
173
174       syms = GetCommandOutput([GetDumpSymsBinary(options.build_dir), '-r',
175                                binary])
176       module_line = re.match("MODULE [^ ]+ [^ ]+ ([0-9A-F]+) (.*)\n", syms)
177       output_path = os.path.join(options.symbols_dir, module_line.group(2),
178                                  module_line.group(1))
179       mkdir_p(output_path)
180       symbol_file = "%s.sym" % module_line.group(2)
181       f = open(os.path.join(output_path, symbol_file), 'w')
182       f.write(syms)
183       f.close()
184
185       queue.task_done()
186
187   for binary in binaries:
188     queue.put(binary)
189
190   for _ in range(options.jobs):
191     t = threading.Thread(target=_Worker)
192     t.daemon = True
193     t.start()
194
195   queue.join()
196
197
198 def main():
199   parser = optparse.OptionParser()
200   parser.add_option('', '--build-dir', default='',
201                     help='The build output directory.')
202   parser.add_option('', '--symbols-dir', default='',
203                     help='The directory where to write the symbols file.')
204   parser.add_option('', '--binary', default='',
205                     help='The path of the binary to generate symbols for.')
206   parser.add_option('', '--clear', default=False, action='store_true',
207                     help='Clear the symbols directory before writing new '
208                          'symbols.')
209   parser.add_option('-j', '--jobs', default=CONCURRENT_TASKS, action='store',
210                     type='int', help='Number of parallel tasks to run.')
211   parser.add_option('-v', '--verbose', action='store_true',
212                     help='Print verbose status output.')
213
214   (options, _) = parser.parse_args()
215
216   if not options.symbols_dir:
217     print "Required option --symbols-dir missing."
218     return 1
219
220   if not options.build_dir:
221     print "Required option --build-dir missing."
222     return 1
223
224   if not options.binary:
225     print "Required option --binary missing."
226     return 1
227
228   if not os.access(options.binary, os.X_OK):
229     print "Cannot find %s." % options.binary
230     return 1
231
232   if options.clear:
233     try:
234       shutil.rmtree(options.symbols_dir)
235     except:
236       pass
237
238   # Build the transitive closure of all dependencies.
239   binaries = set([options.binary])
240   queue = [options.binary]
241   exe_path = os.path.dirname(options.binary)
242   while queue:
243     deps = GetSharedLibraryDependencies(options, queue.pop(0), exe_path)
244     new_deps = set(deps) - binaries
245     binaries |= new_deps
246     queue.extend(list(new_deps))
247
248   GenerateSymbols(options, binaries)
249
250   return 0
251
252
253 if '__main__' == __name__:
254   sys.exit(main())