1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/tools/rb/fix_macosx_stack.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,144 @@ 1.4 +#!/usr/bin/python 1.5 +# vim:sw=4:ts=4:et: 1.6 +# This Source Code Form is subject to the terms of the Mozilla Public 1.7 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.9 + 1.10 +# This script uses atos to process the output of nsTraceRefcnt's Mac OS 1.11 +# X stack walking code. This is useful for two things: 1.12 +# (1) Getting line number information out of 1.13 +# |nsTraceRefcnt::WalkTheStack|'s output in debug builds. 1.14 +# (2) Getting function names out of |nsTraceRefcnt::WalkTheStack|'s 1.15 +# output on all builds (where it mostly prints UNKNOWN because only 1.16 +# a handful of symbols are exported from component libraries). 1.17 +# 1.18 +# Use the script by piping output containing stacks (such as raw stacks 1.19 +# or make-tree.pl balance trees) through this script. 1.20 + 1.21 +import subprocess 1.22 +import sys 1.23 +import re 1.24 +import os 1.25 +import pty 1.26 +import termios 1.27 + 1.28 +class unbufferedLineConverter: 1.29 + """ 1.30 + Wrap a child process that responds to each line of input with one line of 1.31 + output. Uses pty to trick the child into providing unbuffered output. 1.32 + """ 1.33 + def __init__(self, command, args = []): 1.34 + pid, fd = pty.fork() 1.35 + if pid == 0: 1.36 + # We're the child. Transfer control to command. 1.37 + os.execvp(command, [command] + args) 1.38 + else: 1.39 + # Disable echoing. 1.40 + attr = termios.tcgetattr(fd) 1.41 + attr[3] = attr[3] & ~termios.ECHO 1.42 + termios.tcsetattr(fd, termios.TCSANOW, attr) 1.43 + # Set up a file()-like interface to the child process 1.44 + self.r = os.fdopen(fd, "r", 1) 1.45 + self.w = os.fdopen(os.dup(fd), "w", 1) 1.46 + def convert(self, line): 1.47 + self.w.write(line + "\n") 1.48 + return self.r.readline().rstrip("\r\n") 1.49 + @staticmethod 1.50 + def test(): 1.51 + assert unbufferedLineConverter("rev").convert("123") == "321" 1.52 + assert unbufferedLineConverter("cut", ["-c3"]).convert("abcde") == "c" 1.53 + print "Pass" 1.54 + 1.55 +def separate_debug_file_for(file): 1.56 + return None 1.57 + 1.58 +address_adjustments = {} 1.59 +def address_adjustment(file): 1.60 + if not file in address_adjustments: 1.61 + result = None 1.62 + otool = subprocess.Popen(["otool", "-l", file], stdout=subprocess.PIPE) 1.63 + while True: 1.64 + line = otool.stdout.readline() 1.65 + if line == "": 1.66 + break 1.67 + if line == " segname __TEXT\n": 1.68 + line = otool.stdout.readline() 1.69 + if not line.startswith(" vmaddr "): 1.70 + raise StandardError("unexpected otool output") 1.71 + result = int(line[10:], 16) 1.72 + break 1.73 + otool.stdout.close() 1.74 + 1.75 + if result is None: 1.76 + raise StandardError("unexpected otool output") 1.77 + 1.78 + address_adjustments[file] = result 1.79 + 1.80 + return address_adjustments[file] 1.81 + 1.82 +atoses = {} 1.83 +def addressToSymbol(file, address): 1.84 + converter = None 1.85 + if not file in atoses: 1.86 + debug_file = separate_debug_file_for(file) or file 1.87 + converter = unbufferedLineConverter('/usr/bin/xcrun', ['atos', '-arch', 'x86_64', '-o', debug_file]) 1.88 + atoses[file] = converter 1.89 + else: 1.90 + converter = atoses[file] 1.91 + return converter.convert("0x%X" % address) 1.92 + 1.93 +cxxfilt_proc = None 1.94 +def cxxfilt(sym): 1.95 + if cxxfilt_proc is None: 1.96 + # --no-strip-underscores because atos already stripped the underscore 1.97 + globals()["cxxfilt_proc"] = subprocess.Popen(['c++filt', 1.98 + '--no-strip-underscores', 1.99 + '--format', 'gnu-v3'], 1.100 + stdin=subprocess.PIPE, 1.101 + stdout=subprocess.PIPE) 1.102 + cxxfilt_proc.stdin.write(sym + "\n") 1.103 + return cxxfilt_proc.stdout.readline().rstrip("\n") 1.104 + 1.105 +line_re = re.compile("^(.*) ?\[([^ ]*) \+(0x[0-9a-fA-F]{1,8})\](.*)$") 1.106 +balance_tree_re = re.compile("^([ \|0-9-]*)") 1.107 +atos_name_re = re.compile("^(.+) \(in ([^)]+)\) \((.+)\)$") 1.108 + 1.109 +def fixSymbols(line): 1.110 + result = line_re.match(line) 1.111 + if result is not None: 1.112 + # before allows preservation of balance trees 1.113 + # after allows preservation of counts 1.114 + (before, file, address, after) = result.groups() 1.115 + address = int(address, 16) 1.116 + 1.117 + if os.path.exists(file) and os.path.isfile(file): 1.118 + address += address_adjustment(file) 1.119 + info = addressToSymbol(file, address) 1.120 + 1.121 + # atos output seems to have three forms: 1.122 + # address 1.123 + # address (in foo.dylib) 1.124 + # symbol (in foo.dylib) (file:line) 1.125 + name_result = atos_name_re.match(info) 1.126 + if name_result is not None: 1.127 + # Print the first two forms as-is, and transform the third 1.128 + (name, library, fileline) = name_result.groups() 1.129 + # atos demangles, but occasionally it fails. cxxfilt can mop 1.130 + # up the remaining cases(!), which will begin with '_Z'. 1.131 + if (name.startswith("_Z")): 1.132 + name = cxxfilt(name) 1.133 + info = "%s (%s, in %s)" % (name, fileline, library) 1.134 + 1.135 + # throw away the bad symbol, but keep balance tree structure 1.136 + before = balance_tree_re.match(before).groups()[0] 1.137 + 1.138 + return before + info + after + "\n" 1.139 + else: 1.140 + sys.stderr.write("Warning: File \"" + file + "\" does not exist.\n") 1.141 + return line 1.142 + else: 1.143 + return line 1.144 + 1.145 +if __name__ == "__main__": 1.146 + for line in sys.stdin: 1.147 + sys.stdout.write(fixSymbols(line))