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.
6 """A tool to generate symbols for a binary suitable for breakpad.
8 Currently, the tool only supports Linux, Android, and Mac. Support for other
26 def GetCommandOutput(command
):
27 """Runs the command list, returning its output.
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.
34 devnull
= open(os
.devnull
, 'w')
35 proc
= subprocess
.Popen(command
, stdout
=subprocess
.PIPE
, stderr
=devnull
,
37 output
= proc
.communicate()[0]
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_bin
52 def Resolve(path
, exe_path
, loader_path
, rpaths
):
53 """Resolve a dyld path.
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
60 path
= path
.replace('@loader_path', loader_path
)
61 path
= path
.replace('@executable_path', exe_path
)
62 if path
.find('@rpath') != -1:
64 new_path
= Resolve(path
.replace('@rpath', rpath
), exe_path
, loader_path
,
66 if os
.access(new_path
, os
.X_OK
):
72 def GetSharedLibraryDependenciesLinux(binary
):
73 """Return absolute paths to all shared library dependecies of the binary.
75 This implementation assumes that we're running on a Linux system."""
76 ldd
= GetCommandOutput(['ldd', binary
])
77 lib_re
= re
.compile('\t.* => (.+) \(.*\)$')
79 for line
in ldd
.splitlines():
80 m
= lib_re
.match(line
)
82 result
.append(m
.group(1))
86 def GetSharedLibraryDependenciesMac(binary
, exe_path
):
87 """Return absolute paths to all shared library dependecies of the binary.
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()
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))
98 otool
= GetCommandOutput(['otool', '-L', binary
]).splitlines()
99 lib_re
= re
.compile('\t(.*) \(compatibility .*\)$')
102 m
= lib_re
.match(line
)
104 dep
= Resolve(m
.group(1), exe_path
, loader_path
, rpaths
)
106 deps
.append(os
.path
.normpath(dep
))
110 def GetSharedLibraryDependencies(options
, binary
, exe_path
):
111 """Return absolute paths to all shared library dependecies of the binary."""
113 if sys
.platform
.startswith('linux'):
114 deps
= GetSharedLibraryDependenciesLinux(binary
)
115 elif sys
.platform
== 'darwin':
116 deps
= GetSharedLibraryDependenciesMac(binary
, exe_path
)
118 print "Platform not supported."
122 build_dir
= os
.path
.abspath(options
.build_dir
)
124 if (os
.access(dep
, os
.X_OK
) and
125 os
.path
.abspath(os
.path
.dirname(dep
)).startswith(build_dir
)):
131 """Simulates mkdir -p."""
135 if e
.errno
== errno
.EEXIST
and os
.path
.isdir(path
):
140 def GenerateSymbols(options
, binaries
):
141 """Dumps the symbols of binary and places them in the given directory."""
143 queue
= Queue
.Queue()
144 print_lock
= threading
.Lock()
150 should_dump_syms
= True
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"
160 if not should_dump_syms
:
163 print "Skipping %s (%s)" % (binary
, reason
)
169 print "Generating symbols for %s" % binary
171 if os
.path
.isdir(output_path
):
172 os
.utime(output_path
, None)
174 syms
= GetCommandOutput([GetDumpSymsBinary(options
.build_dir
), '-r',
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))
180 symbol_file
= "%s.sym" % module_line
.group(2)
182 f
= open(os
.path
.join(output_path
, symbol_file
), 'w')
186 # Not much we can do about this.
192 for binary
in binaries
:
195 for _
in range(options
.jobs
):
196 t
= threading
.Thread(target
=_Worker
)
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 '
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.')
219 (options
, _
) = parser
.parse_args()
221 if not options
.symbols_dir
:
222 print "Required option --symbols-dir missing."
225 if not options
.build_dir
:
226 print "Required option --build-dir missing."
229 if not options
.binary
:
230 print "Required option --binary missing."
233 if not os
.access(options
.binary
, os
.X_OK
):
234 print "Cannot find %s." % options
.binary
239 shutil
.rmtree(options
.symbols_dir
)
243 if not GetDumpSymsBinary(options
.build_dir
):
246 # Build the transitive closure of all dependencies.
247 binaries
= set([options
.binary
])
248 queue
= [options
.binary
]
249 exe_path
= os
.path
.dirname(options
.binary
)
251 deps
= GetSharedLibraryDependencies(options
, queue
.pop(0), exe_path
)
252 new_deps
= set(deps
) - binaries
254 queue
.extend(list(new_deps
))
256 GenerateSymbols(options
, binaries
)
261 if '__main__' == __name__
: