Upstream version 10.38.222.0
[platform/framework/web/crosswalk.git] / src / components / crash / 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       try:
182         f = open(os.path.join(output_path, symbol_file), 'w')
183         f.write(syms)
184         f.close()
185       except Exception, e:
186         # Not much we can do about this.
187         with print_lock:
188           print e
189
190       queue.task_done()
191
192   for binary in binaries:
193     queue.put(binary)
194
195   for _ in range(options.jobs):
196     t = threading.Thread(target=_Worker)
197     t.daemon = True
198     t.start()
199
200   queue.join()
201
202
203 def main():
204   parser = optparse.OptionParser()
205   parser.add_option('', '--build-dir', default='',
206                     help='The build output directory.')
207   parser.add_option('', '--symbols-dir', default='',
208                     help='The directory where to write the symbols file.')
209   parser.add_option('', '--binary', default='',
210                     help='The path of the binary to generate symbols for.')
211   parser.add_option('', '--clear', default=False, action='store_true',
212                     help='Clear the symbols directory before writing new '
213                          'symbols.')
214   parser.add_option('-j', '--jobs', default=CONCURRENT_TASKS, action='store',
215                     type='int', help='Number of parallel tasks to run.')
216   parser.add_option('-v', '--verbose', action='store_true',
217                     help='Print verbose status output.')
218
219   (options, _) = parser.parse_args()
220
221   if not options.symbols_dir:
222     print "Required option --symbols-dir missing."
223     return 1
224
225   if not options.build_dir:
226     print "Required option --build-dir missing."
227     return 1
228
229   if not options.binary:
230     print "Required option --binary missing."
231     return 1
232
233   if not os.access(options.binary, os.X_OK):
234     print "Cannot find %s." % options.binary
235     return 1
236
237   if options.clear:
238     try:
239       shutil.rmtree(options.symbols_dir)
240     except:
241       pass
242
243   # Build the transitive closure of all dependencies.
244   binaries = set([options.binary])
245   queue = [options.binary]
246   exe_path = os.path.dirname(options.binary)
247   while queue:
248     deps = GetSharedLibraryDependencies(options, queue.pop(0), exe_path)
249     new_deps = set(deps) - binaries
250     binaries |= new_deps
251     queue.extend(list(new_deps))
252
253   GenerateSymbols(options, binaries)
254
255   return 0
256
257
258 if '__main__' == __name__:
259   sys.exit(main())