Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(730)

Side by Side Diff: third_party/android_platform/development/scripts/symbol.py

Issue 18715002: Android: apply some optimizations to the stack symbolization tools. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/android_platform/development/scripts/stack_core.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # 2 #
3 # Copyright (C) 2013 The Android Open Source Project 3 # Copyright (C) 2013 The Android Open Source Project
4 # 4 #
5 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License. 6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at 7 # You may obtain a copy of the License at
8 # 8 #
9 # http://www.apache.org/licenses/LICENSE-2.0 9 # http://www.apache.org/licenses/LICENSE-2.0
10 # 10 #
11 # Unless required by applicable law or agreed to in writing, software 11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS, 12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and 14 # See the License for the specific language governing permissions and
15 # limitations under the License. 15 # limitations under the License.
16 16
17 """Module for looking up symbolic debugging information. 17 """Module for looking up symbolic debugging information.
18 18
19 The information can include symbol names, offsets, and source locations. 19 The information can include symbol names, offsets, and source locations.
20 """ 20 """
21 21
22 import os 22 import os
23 import re 23 import re
24 import subprocess 24 import subprocess
25 25
26 ANDROID_BUILD_TOP = os.environ["ANDROID_BUILD_TOP"] 26 CHROME_SRC = os.path.join(os.path.realpath(os.path.dirname(__file__)),
27 if not ANDROID_BUILD_TOP: 27 os.pardir, os.pardir, os.pardir, os.pardir)
28 ANDROID_BUILD_TOP = "." 28 ANDROID_BUILD_TOP = CHROME_SRC
29 29 SYMBOLS_DIR = CHROME_SRC
30 def FindSymbolsDir(): 30 CHROME_SYMBOLS_DIR = CHROME_SRC
31 saveddir = os.getcwd()
32 os.chdir(ANDROID_BUILD_TOP)
33 try:
34 cmd = ("CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core "
35 "SRC_TARGET_DIR=build/target make -f build/core/config.mk "
36 "dumpvar-abs-TARGET_OUT_UNSTRIPPED")
37 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).stdout
38 return os.path.join(ANDROID_BUILD_TOP, stream.read().strip())
39 finally:
40 os.chdir(saveddir)
41
42 SYMBOLS_DIR = FindSymbolsDir()
43 31
44 ARCH = "arm" 32 ARCH = "arm"
45 33
46 TOOLCHAIN_INFO = None 34 TOOLCHAIN_INFO = None
47 35
48 def Uname(): 36 def Uname():
49 """'uname' for constructing prebuilt/<...> and out/host/<...> paths.""" 37 """'uname' for constructing prebuilt/<...> and out/host/<...> paths."""
50 uname = os.uname()[0] 38 uname = os.uname()[0]
51 if uname == "Darwin": 39 if uname == "Darwin":
52 proc = os.uname()[-1] 40 proc = os.uname()[-1]
53 if proc == "i386" or proc == "x86_64": 41 if proc == "i386" or proc == "x86_64":
54 return "darwin-x86" 42 return "darwin-x86"
55 return "darwin-ppc" 43 return "darwin-ppc"
56 if uname == "Linux": 44 if uname == "Linux":
57 return "linux-x86" 45 return "linux-x86"
58 return uname 46 return uname
59 47
60 def ToolPath(tool, toolchain_info=None): 48 def ToolPath(tool, toolchain_info=None):
61 """Return a full qualified path to the specified tool""" 49 """Return a full qualified path to the specified tool"""
62 if not toolchain_info: 50 # ToolPath looks for the tools in the completely incorrect directory.
63 toolchain_info = FindToolchain() 51 # This looks in the checked in android_tools.
64 (label, platform, target) = toolchain_info 52 if ARCH == "arm":
65 return os.path.join(ANDROID_BUILD_TOP, "prebuilts/gcc", Uname(), platform, lab el, "bin", 53 toolchain_source = "arm-linux-androideabi-4.6"
66 target + "-" + tool) 54 toolchain_prefix = "arm-linux-androideabi"
55 else:
56 toolchain_source = "x86-4.6"
57 toolchain_prefix = "i686-android-linux"
58
59 toolchain_subdir = (
60 "third_party/android_tools/ndk/toolchains/%s/prebuilt/linux-x86_64/bin" %
61 toolchain_source)
62
63 return os.path.join(CHROME_SRC,
64 toolchain_subdir,
65 toolchain_prefix + "-" + tool)
67 66
68 def FindToolchain(): 67 def FindToolchain():
69 """Look for the latest available toolchain 68 """Look for the latest available toolchain
70 69
71 Args: 70 Args:
72 None 71 None
73 72
74 Returns: 73 Returns:
75 A pair of strings containing toolchain label and target prefix. 74 A pair of strings containing toolchain label and target prefix.
76 """ 75 """
77 global TOOLCHAIN_INFO 76 global TOOLCHAIN_INFO
78 if TOOLCHAIN_INFO is not None: 77 if TOOLCHAIN_INFO is not None:
79 return TOOLCHAIN_INFO 78 return TOOLCHAIN_INFO
80 79
81 ## Known toolchains, newer ones in the front. 80 ## Known toolchains, newer ones in the front.
82 if ARCH == "arm": 81 if ARCH == "arm":
83 gcc_version = os.environ["TARGET_GCC_VERSION"]
84 known_toolchains = [ 82 known_toolchains = [
85 ("arm-linux-androideabi-" + gcc_version, "arm", "arm-linux-androideabi"), 83 ("arm-linux-androideabi-4.6", "arm", "arm-linux-androideabi"),
86 ] 84 ]
87 elif ARCH =="x86": 85 elif ARCH =="x86":
88 known_toolchains = [ 86 known_toolchains = [
89 ("i686-android-linux-4.4.3", "x86", "i686-android-linux") 87 ("i686-android-linux-4.4.3", "x86", "i686-android-linux")
90 ] 88 ]
91 else: 89 else:
92 known_toolchains = [] 90 known_toolchains = []
93 91
94 # Look for addr2line to check for valid toolchain path. 92 # Look for addr2line to check for valid toolchain path.
95 for (label, platform, target) in known_toolchains: 93 for (label, platform, target) in known_toolchains:
96 toolchain_info = (label, platform, target); 94 toolchain_info = (label, platform, target);
97 if os.path.exists(ToolPath("addr2line", toolchain_info)): 95 if os.path.exists(ToolPath("addr2line", toolchain_info)):
98 TOOLCHAIN_INFO = toolchain_info 96 TOOLCHAIN_INFO = toolchain_info
99 return toolchain_info 97 return toolchain_info
100 98
101 raise Exception("Could not find tool chain") 99 raise Exception("Could not find tool chain")
102 100
103 def SymbolInformation(lib, addr): 101 def TranslateLibPath(lib):
102 # SymbolInformation(lib, addr) receives lib as the path from symbols
103 # root to the symbols file. This needs to be translated to point to the
104 # correct .so path. If the user doesn't explicitly specify which directory to
105 # use, then use the most recently updated one in one of the known directories.
106 # If the .so is not found somewhere in CHROME_SYMBOLS_DIR, leave it
107 # untranslated in case it is an Android symbol in SYMBOLS_DIR.
108 library_name = os.path.basename(lib)
109 candidate_dirs = ['.',
110 'out/Debug/lib',
111 'out/Debug/lib.target',
112 'out/Release/lib',
113 'out/Release/lib.target',
114 ]
115
116 candidate_libraries = map(
117 lambda d: ('%s/%s/%s' % (CHROME_SYMBOLS_DIR, d, library_name)),
118 candidate_dirs)
119 candidate_libraries = filter(os.path.exists, candidate_libraries)
120 candidate_libraries = sorted(candidate_libraries,
121 key=os.path.getmtime, reverse=True)
122
123 if not candidate_libraries:
124 return lib
125
126 library_path = os.path.relpath(candidate_libraries[0], SYMBOLS_DIR)
127 return '/' + library_path
128
129 def SymbolInformation(lib, addr, get_detailed_info):
104 """Look up symbol information about an address. 130 """Look up symbol information about an address.
105 131
106 Args: 132 Args:
107 lib: library (or executable) pathname containing symbols 133 lib: library (or executable) pathname containing symbols
108 addr: string hexidecimal address 134 addr: string hexidecimal address
109 135
110 Returns: 136 Returns:
111 A list of the form [(source_symbol, source_location, 137 A list of the form [(source_symbol, source_location,
112 object_symbol_with_offset)]. 138 object_symbol_with_offset)].
113 139
114 If the function has been inlined then the list may contain 140 If the function has been inlined then the list may contain
115 more than one element with the symbols for the most deeply 141 more than one element with the symbols for the most deeply
116 nested inlined location appearing first. The list is 142 nested inlined location appearing first. The list is
117 always non-empty, even if no information is available. 143 always non-empty, even if no information is available.
118 144
119 Usually you want to display the source_location and 145 Usually you want to display the source_location and
120 object_symbol_with_offset from the last element in the list. 146 object_symbol_with_offset from the last element in the list.
121 """ 147 """
122 info = SymbolInformationForSet(lib, set([addr])) 148 lib = TranslateLibPath(lib)
149 info = SymbolInformationForSet(lib, set([addr]), get_detailed_info)
123 return (info and info.get(addr)) or [(None, None, None)] 150 return (info and info.get(addr)) or [(None, None, None)]
124 151
125 152
126 def SymbolInformationForSet(lib, unique_addrs): 153 def SymbolInformationForSet(lib, unique_addrs, get_detailed_info):
127 """Look up symbol information for a set of addresses from the given library. 154 """Look up symbol information for a set of addresses from the given library.
128 155
129 Args: 156 Args:
130 lib: library (or executable) pathname containing symbols 157 lib: library (or executable) pathname containing symbols
131 unique_addrs: set of hexidecimal addresses 158 unique_addrs: set of hexidecimal addresses
132 159
133 Returns: 160 Returns:
134 A dictionary of the form {addr: [(source_symbol, source_location, 161 A dictionary of the form {addr: [(source_symbol, source_location,
135 object_symbol_with_offset)]} where each address has a list of 162 object_symbol_with_offset)]} where each address has a list of
136 associated symbols and locations. The list is always non-empty. 163 associated symbols and locations. The list is always non-empty.
137 164
138 If the function has been inlined then the list may contain 165 If the function has been inlined then the list may contain
139 more than one element with the symbols for the most deeply 166 more than one element with the symbols for the most deeply
140 nested inlined location appearing first. The list is 167 nested inlined location appearing first. The list is
141 always non-empty, even if no information is available. 168 always non-empty, even if no information is available.
142 169
143 Usually you want to display the source_location and 170 Usually you want to display the source_location and
144 object_symbol_with_offset from the last element in the list. 171 object_symbol_with_offset from the last element in the list.
145 """ 172 """
146 if not lib: 173 if not lib:
147 return None 174 return None
148 175
149 addr_to_line = CallAddr2LineForSet(lib, unique_addrs) 176 addr_to_line = CallAddr2LineForSet(lib, unique_addrs)
150 if not addr_to_line: 177 if not addr_to_line:
151 return None 178 return None
152 179
153 addr_to_objdump = CallObjdumpForSet(lib, unique_addrs) 180 if get_detailed_info:
154 if not addr_to_objdump: 181 addr_to_objdump = CallObjdumpForSet(lib, unique_addrs)
155 return None 182 if not addr_to_objdump:
183 return None
184 else:
185 addr_to_objdump = dict((addr, ("", 0)) for addr in unique_addrs)
156 186
157 result = {} 187 result = {}
158 for addr in unique_addrs: 188 for addr in unique_addrs:
159 source_info = addr_to_line.get(addr) 189 source_info = addr_to_line.get(addr)
160 if not source_info: 190 if not source_info:
161 source_info = [(None, None)] 191 source_info = [(None, None)]
162 if addr in addr_to_objdump: 192 if addr in addr_to_objdump:
163 (object_symbol, object_offset) = addr_to_objdump.get(addr) 193 (object_symbol, object_offset) = addr_to_objdump.get(addr)
164 object_symbol_with_offset = FormatSymbolWithOffset(object_symbol, 194 object_symbol_with_offset = FormatSymbolWithOffset(object_symbol,
165 object_offset) 195 object_offset)
166 else: 196 else:
167 object_symbol_with_offset = None 197 object_symbol_with_offset = None
168 result[addr] = [(source_symbol, source_location, object_symbol_with_offset) 198 result[addr] = [(source_symbol, source_location, object_symbol_with_offset)
169 for (source_symbol, source_location) in source_info] 199 for (source_symbol, source_location) in source_info]
170 200
171 return result 201 return result
172 202
173 203
204 class MemoizedForSet(object):
205 def __init__(self, fn):
206 self.fn = fn
207 self.cache = {}
208
209 def __call__(self, lib, unique_addrs):
210 lib_cache = self.cache.setdefault(lib, {})
211
212 no_cache = filter(lambda x: x not in lib_cache, unique_addrs)
213 if no_cache:
214 lib_cache.update((k, None) for k in no_cache)
215 result = self.fn(lib, no_cache)
216 if result:
217 lib_cache.update(result)
218
219 return dict((k, lib_cache[k]) for k in unique_addrs if lib_cache[k])
220
221
222 @MemoizedForSet
174 def CallAddr2LineForSet(lib, unique_addrs): 223 def CallAddr2LineForSet(lib, unique_addrs):
175 """Look up line and symbol information for a set of addresses. 224 """Look up line and symbol information for a set of addresses.
176 225
177 Args: 226 Args:
178 lib: library (or executable) pathname containing symbols 227 lib: library (or executable) pathname containing symbols
179 unique_addrs: set of string hexidecimal addresses look up. 228 unique_addrs: set of string hexidecimal addresses look up.
180 229
181 Returns: 230 Returns:
182 A dictionary of the form {addr: [(symbol, file:line)]} where 231 A dictionary of the form {addr: [(symbol, file:line)]} where
183 each address has a list of associated symbols and locations 232 each address has a list of associated symbols and locations
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
237 286
238 Returns: 287 Returns:
239 The stripped program counter address. 288 The stripped program counter address.
240 """ 289 """
241 global ARCH 290 global ARCH
242 291
243 if ARCH == "arm": 292 if ARCH == "arm":
244 return addr & ~1 293 return addr & ~1
245 return addr 294 return addr
246 295
296 @MemoizedForSet
247 def CallObjdumpForSet(lib, unique_addrs): 297 def CallObjdumpForSet(lib, unique_addrs):
248 """Use objdump to find out the names of the containing functions. 298 """Use objdump to find out the names of the containing functions.
249 299
250 Args: 300 Args:
251 lib: library (or executable) pathname containing symbols 301 lib: library (or executable) pathname containing symbols
252 unique_addrs: set of string hexidecimal addresses to find the functions for. 302 unique_addrs: set of string hexidecimal addresses to find the functions for.
253 303
254 Returns: 304 Returns:
255 A dictionary of the form {addr: (string symbol, offset)}. 305 A dictionary of the form {addr: (string symbol, offset)}.
256 """ 306 """
257 if not lib: 307 if not lib:
258 return None 308 return None
259 309
260 symbols = SYMBOLS_DIR + lib 310 symbols = SYMBOLS_DIR + lib
261 if not os.path.exists(symbols): 311 if not os.path.exists(symbols):
262 return None 312 return None
263 313
264 symbols = SYMBOLS_DIR + lib 314 symbols = SYMBOLS_DIR + lib
265 if not os.path.exists(symbols): 315 if not os.path.exists(symbols):
266 return None 316 return None
267 317
268 addrs = sorted(unique_addrs) 318 result = {}
269 start_addr_dec = str(StripPC(int(addrs[0], 16)))
270 stop_addr_dec = str(StripPC(int(addrs[-1], 16)) + 8)
271 cmd = [ToolPath("objdump"),
272 "--section=.text",
273 "--demangle",
274 "--disassemble",
275 "--start-address=" + start_addr_dec,
276 "--stop-address=" + stop_addr_dec,
277 symbols]
278 319
279 # Function lines look like: 320 # Function lines look like:
280 # 000177b0 <android::IBinder::~IBinder()+0x2c>: 321 # 000177b0 <android::IBinder::~IBinder()+0x2c>:
281 # We pull out the address and function first. Then we check for an optional 322 # We pull out the address and function first. Then we check for an optional
282 # offset. This is tricky due to functions that look like "operator+(..)+0x2c" 323 # offset. This is tricky due to functions that look like "operator+(..)+0x2c"
283 func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$") 324 func_regexp = re.compile("(^[a-f0-9]*) \<(.*)\>:$")
284 offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)") 325 offset_regexp = re.compile("(.*)\+0x([a-f0-9]*)")
285 326
286 # A disassembly line looks like: 327 # A disassembly line looks like:
287 # 177b2:» b510 » push» {r4, lr} 328 # 177b2: b510 push {r4, lr}
288 asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$") 329 asm_regexp = re.compile("(^[ a-f0-9]*):[ a-f0-0]*.*$")
289 330
290 current_symbol = None # The current function symbol in the disassembly. 331 for target_addr in unique_addrs:
291 current_symbol_addr = 0 # The address of the current function. 332 start_addr_dec = str(StripPC(int(target_addr, 16)))
292 addr_index = 0 # The address that we are currently looking for. 333 stop_addr_dec = str(StripPC(int(target_addr, 16)) + 8)
334 cmd = [ToolPath("objdump"),
335 "--section=.text",
336 "--demangle",
337 "--disassemble",
338 "--start-address=" + start_addr_dec,
339 "--stop-address=" + stop_addr_dec,
340 symbols]
293 341
294 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout 342 current_symbol = None # The current function symbol in the disassembly.
295 result = {} 343 current_symbol_addr = 0 # The address of the current function.
296 for line in stream:
297 # Is it a function line like:
298 # 000177b0 <android::IBinder::~IBinder()>:
299 components = func_regexp.match(line)
300 if components:
301 # This is a new function, so record the current function and its address.
302 current_symbol_addr = int(components.group(1), 16)
303 current_symbol = components.group(2)
304 344
305 # Does it have an optional offset like: "foo(..)+0x2c"? 345 stream = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
306 components = offset_regexp.match(current_symbol) 346 for line in stream:
347 # Is it a function line like:
348 # 000177b0 <android::IBinder::~IBinder()>:
349 components = func_regexp.match(line)
307 if components: 350 if components:
308 current_symbol = components.group(1) 351 # This is a new function, so record the current function and its address .
309 offset = components.group(2) 352 current_symbol_addr = int(components.group(1), 16)
310 if offset: 353 current_symbol = components.group(2)
311 current_symbol_addr -= int(offset, 16)
312 354
313 # Is it an disassembly line like: 355 # Does it have an optional offset like: "foo(..)+0x2c"?
314 # 177b2:» b510 » push» {r4, lr} 356 components = offset_regexp.match(current_symbol)
315 components = asm_regexp.match(line) 357 if components:
316 if components: 358 current_symbol = components.group(1)
317 addr = components.group(1) 359 offset = components.group(2)
318 target_addr = addrs[addr_index] 360 if offset:
319 i_addr = int(addr, 16) 361 current_symbol_addr -= int(offset, 16)
320 i_target = StripPC(int(target_addr, 16)) 362
321 if i_addr == i_target: 363 # Is it an disassembly line like:
322 result[target_addr] = (current_symbol, i_target - current_symbol_addr) 364 # 177b2: b510 push {r4, lr}
323 addr_index += 1 365 components = asm_regexp.match(line)
324 if addr_index >= len(addrs): 366 if components:
325 break 367 addr = components.group(1)
326 stream.close() 368 i_addr = int(addr, 16)
369 i_target = StripPC(int(target_addr, 16))
370 if i_addr == i_target:
371 result[target_addr] = (current_symbol, i_target - current_symbol_addr)
372 stream.close()
327 373
328 return result 374 return result
329 375
330 376
331 def CallCppFilt(mangled_symbol): 377 def CallCppFilt(mangled_symbol):
332 cmd = [ToolPath("c++filt")] 378 cmd = [ToolPath("c++filt")]
333 process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE) 379 process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
334 process.stdin.write(mangled_symbol) 380 process.stdin.write(mangled_symbol)
335 process.stdin.write("\n") 381 process.stdin.write("\n")
336 process.stdin.close() 382 process.stdin.close()
337 demangled_symbol = process.stdout.readline().strip() 383 demangled_symbol = process.stdout.readline().strip()
338 process.stdout.close() 384 process.stdout.close()
339 return demangled_symbol 385 return demangled_symbol
340 386
341 def FormatSymbolWithOffset(symbol, offset): 387 def FormatSymbolWithOffset(symbol, offset):
342 if offset == 0: 388 if offset == 0:
343 return symbol 389 return symbol
344 return "%s+%d" % (symbol, offset) 390 return "%s+%d" % (symbol, offset)
OLDNEW
« no previous file with comments | « third_party/android_platform/development/scripts/stack_core.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698