tools/rb/fix_macosx_stack.py

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:dbf7d1d66507
1 #!/usr/bin/python
2 # vim:sw=4:ts=4:et:
3 # This Source Code Form is subject to the terms of the Mozilla Public
4 # License, v. 2.0. If a copy of the MPL was not distributed with this
5 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7 # This script uses atos to process the output of nsTraceRefcnt's Mac OS
8 # X stack walking code. This is useful for two things:
9 # (1) Getting line number information out of
10 # |nsTraceRefcnt::WalkTheStack|'s output in debug builds.
11 # (2) Getting function names out of |nsTraceRefcnt::WalkTheStack|'s
12 # output on all builds (where it mostly prints UNKNOWN because only
13 # a handful of symbols are exported from component libraries).
14 #
15 # Use the script by piping output containing stacks (such as raw stacks
16 # or make-tree.pl balance trees) through this script.
17
18 import subprocess
19 import sys
20 import re
21 import os
22 import pty
23 import termios
24
25 class unbufferedLineConverter:
26 """
27 Wrap a child process that responds to each line of input with one line of
28 output. Uses pty to trick the child into providing unbuffered output.
29 """
30 def __init__(self, command, args = []):
31 pid, fd = pty.fork()
32 if pid == 0:
33 # We're the child. Transfer control to command.
34 os.execvp(command, [command] + args)
35 else:
36 # Disable echoing.
37 attr = termios.tcgetattr(fd)
38 attr[3] = attr[3] & ~termios.ECHO
39 termios.tcsetattr(fd, termios.TCSANOW, attr)
40 # Set up a file()-like interface to the child process
41 self.r = os.fdopen(fd, "r", 1)
42 self.w = os.fdopen(os.dup(fd), "w", 1)
43 def convert(self, line):
44 self.w.write(line + "\n")
45 return self.r.readline().rstrip("\r\n")
46 @staticmethod
47 def test():
48 assert unbufferedLineConverter("rev").convert("123") == "321"
49 assert unbufferedLineConverter("cut", ["-c3"]).convert("abcde") == "c"
50 print "Pass"
51
52 def separate_debug_file_for(file):
53 return None
54
55 address_adjustments = {}
56 def address_adjustment(file):
57 if not file in address_adjustments:
58 result = None
59 otool = subprocess.Popen(["otool", "-l", file], stdout=subprocess.PIPE)
60 while True:
61 line = otool.stdout.readline()
62 if line == "":
63 break
64 if line == " segname __TEXT\n":
65 line = otool.stdout.readline()
66 if not line.startswith(" vmaddr "):
67 raise StandardError("unexpected otool output")
68 result = int(line[10:], 16)
69 break
70 otool.stdout.close()
71
72 if result is None:
73 raise StandardError("unexpected otool output")
74
75 address_adjustments[file] = result
76
77 return address_adjustments[file]
78
79 atoses = {}
80 def addressToSymbol(file, address):
81 converter = None
82 if not file in atoses:
83 debug_file = separate_debug_file_for(file) or file
84 converter = unbufferedLineConverter('/usr/bin/xcrun', ['atos', '-arch', 'x86_64', '-o', debug_file])
85 atoses[file] = converter
86 else:
87 converter = atoses[file]
88 return converter.convert("0x%X" % address)
89
90 cxxfilt_proc = None
91 def cxxfilt(sym):
92 if cxxfilt_proc is None:
93 # --no-strip-underscores because atos already stripped the underscore
94 globals()["cxxfilt_proc"] = subprocess.Popen(['c++filt',
95 '--no-strip-underscores',
96 '--format', 'gnu-v3'],
97 stdin=subprocess.PIPE,
98 stdout=subprocess.PIPE)
99 cxxfilt_proc.stdin.write(sym + "\n")
100 return cxxfilt_proc.stdout.readline().rstrip("\n")
101
102 line_re = re.compile("^(.*) ?\[([^ ]*) \+(0x[0-9a-fA-F]{1,8})\](.*)$")
103 balance_tree_re = re.compile("^([ \|0-9-]*)")
104 atos_name_re = re.compile("^(.+) \(in ([^)]+)\) \((.+)\)$")
105
106 def fixSymbols(line):
107 result = line_re.match(line)
108 if result is not None:
109 # before allows preservation of balance trees
110 # after allows preservation of counts
111 (before, file, address, after) = result.groups()
112 address = int(address, 16)
113
114 if os.path.exists(file) and os.path.isfile(file):
115 address += address_adjustment(file)
116 info = addressToSymbol(file, address)
117
118 # atos output seems to have three forms:
119 # address
120 # address (in foo.dylib)
121 # symbol (in foo.dylib) (file:line)
122 name_result = atos_name_re.match(info)
123 if name_result is not None:
124 # Print the first two forms as-is, and transform the third
125 (name, library, fileline) = name_result.groups()
126 # atos demangles, but occasionally it fails. cxxfilt can mop
127 # up the remaining cases(!), which will begin with '_Z'.
128 if (name.startswith("_Z")):
129 name = cxxfilt(name)
130 info = "%s (%s, in %s)" % (name, fileline, library)
131
132 # throw away the bad symbol, but keep balance tree structure
133 before = balance_tree_re.match(before).groups()[0]
134
135 return before + info + after + "\n"
136 else:
137 sys.stderr.write("Warning: File \"" + file + "\" does not exist.\n")
138 return line
139 else:
140 return line
141
142 if __name__ == "__main__":
143 for line in sys.stdin:
144 sys.stdout.write(fixSymbols(line))

mercurial