tools/rb/fix_macosx_stack.py

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rwxr-xr-x

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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/.
     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.
    18 import subprocess
    19 import sys
    20 import re
    21 import os
    22 import pty
    23 import termios
    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"
    52 def separate_debug_file_for(file):
    53     return None
    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()
    72         if result is None:
    73             raise StandardError("unexpected otool output")
    75         address_adjustments[file] = result
    77     return address_adjustments[file]
    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)
    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")
   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 ([^)]+)\) \((.+)\)$")
   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)
   114         if os.path.exists(file) and os.path.isfile(file):
   115             address += address_adjustment(file)
   116             info = addressToSymbol(file, address)
   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)
   132             # throw away the bad symbol, but keep balance tree structure
   133             before = balance_tree_re.match(before).groups()[0]
   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
   142 if __name__ == "__main__":
   143     for line in sys.stdin:
   144         sys.stdout.write(fixSymbols(line))

mercurial