DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Mercurial (cdf352f02ac4)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
#!/usr/bin/python
# vim:sw=4:ts=4:et:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# This script uses |atos| to post-process the entries produced by
# NS_FormatCodeAddress(), which on Mac often lack a file name and a line
# number.

from __future__ import absolute_import, print_function

import json
import os
import pty
import re
import subprocess
import sys
import termios


class unbufferedLineConverter:
    """
    Wrap a child process that responds to each line of input with one line of
    output.  Uses pty to trick the child into providing unbuffered output.
    """

    def __init__(self, command, args=[]):
        pid, fd = pty.fork()
        if pid == 0:
            # We're the child.  Transfer control to command.
            os.execvp(command, [command] + args)
        else:
            # Disable echoing.
            attr = termios.tcgetattr(fd)
            attr[3] = attr[3] & ~termios.ECHO
            termios.tcsetattr(fd, termios.TCSANOW, attr)
            # Set up a file()-like interface to the child process
            self.r = os.fdopen(fd, "r", 1)
            self.w = os.fdopen(os.dup(fd), "w", 1)

    def convert(self, line):
        self.w.write(line + "\n")
        return self.r.readline().rstrip("\r\n")

    @staticmethod
    def test():
        assert unbufferedLineConverter("rev").convert("123") == "321"
        assert unbufferedLineConverter("cut", ["-c3"]).convert("abcde") == "c"
        print("Pass")


def separate_debug_file_for(file):
    return None


address_adjustments = {}


def address_adjustment(file):
    if file not in address_adjustments:
        result = None
        otool = subprocess.Popen(["otool", "-l", file], stdout=subprocess.PIPE)
        while True:
            line = otool.stdout.readline()
            if line == "":
                break
            if line == "  segname __TEXT\n":
                line = otool.stdout.readline()
                if not line.startswith("   vmaddr "):
                    raise Exception("unexpected otool output")
                result = int(line[10:], 16)
                break
        otool.stdout.close()

        if result is None:
            raise Exception("unexpected otool output")

        address_adjustments[file] = result

    return address_adjustments[file]


atoses = {}


def addressToSymbol(file, address):
    converter = None
    if file not in atoses:
        debug_file = separate_debug_file_for(file) or file
        converter = unbufferedLineConverter(
            '/usr/bin/xcrun', ['atos', '-arch', 'x86_64', '-o', debug_file])
        atoses[file] = converter
    else:
        converter = atoses[file]
    return converter.convert("0x%X" % address)


cxxfilt_proc = None


def cxxfilt(sym):
    if cxxfilt_proc is None:
        # --no-strip-underscores because atos already stripped the underscore
        globals()["cxxfilt_proc"] = subprocess.Popen(['c++filt',
                                                      '--no-strip-underscores',
                                                      '--format', 'gnu-v3'],
                                                     stdin=subprocess.PIPE,
                                                     stdout=subprocess.PIPE)
    cxxfilt_proc.stdin.write(sym + "\n")
    return cxxfilt_proc.stdout.readline().rstrip("\n")


# Matches lines produced by NS_FormatCodeAddress().
line_re = re.compile("^(.*#\d+: )(.+)\[(.+) \+(0x[0-9A-Fa-f]+)\](.*)$")
atos_name_re = re.compile("^(.+) \(in ([^)]+)\) \((.+)\)$")


def fixSymbols(line, jsonEscape=False):
    result = line_re.match(line)
    if result is not None:
        (before, fn, file, address, after) = result.groups()
        address = int(address, 16)

        if os.path.exists(file) and os.path.isfile(file):
            address += address_adjustment(file)
            info = addressToSymbol(file, address)

            # atos output seems to have three forms:
            #   address
            #   address (in foo.dylib)
            #   symbol (in foo.dylib) (file:line)
            name_result = atos_name_re.match(info)
            if name_result is not None:
                # Print the first two forms as-is, and transform the third
                (name, library, fileline) = name_result.groups()
                # atos demangles, but occasionally it fails.  cxxfilt can mop
                # up the remaining cases(!), which will begin with '_Z'.
                if (name.startswith("_Z")):
                    name = cxxfilt(name)
                info = "%s (%s, in %s)" % (name, fileline, library)

            if jsonEscape:
                info = json.dumps(info)[1:-1]   # [1:-1] strips the quotes

            nl = '\n' if line[-1] == '\n' else ''
            return before + info + after + nl
        else:
            sys.stderr.write("Warning: File \"" + file + "\" does not exist.\n")
            return line
    else:
        return line


if __name__ == "__main__":
    for line in sys.stdin:
        sys.stdout.write(fixSymbols(line))