tools/rb/fix_macosx_stack.py

changeset 0
6474c204b198
     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))

mercurial