[ASan] Cache atos processes to make Darwin symbolication faster.

Patch by Jesse Ruderman<jruderman@gmail.com>



git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@185325 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/lib/asan/scripts/asan_symbolize.py b/lib/asan/scripts/asan_symbolize.py
index 207928f..81c9b96 100755
--- a/lib/asan/scripts/asan_symbolize.py
+++ b/lib/asan/scripts/asan_symbolize.py
@@ -10,9 +10,11 @@
 import bisect
 import getopt
 import os
+import pty
 import re
 import subprocess
 import sys
+import termios
 
 llvm_symbolizer = None
 symbolizers = {}
@@ -135,6 +137,33 @@
     return ['%s in %s %s' % (addr, function_name, file_name)]
 
 
+class UnbufferedLineConverter(object):
+  """
+  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, args):
+    pid, fd = pty.fork()
+    if pid == 0:
+      # We're the child.  Transfer control to command.
+      os.execvp(args[0], 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.readline()
+
+  def readline(self):
+    return self.r.readline().rstrip()
+
+
 class DarwinSymbolizer(Symbolizer):
   def __init__(self, addr, binary):
     super(DarwinSymbolizer, self).__init__()
@@ -144,28 +173,21 @@
       self.arch = 'x86_64'
     else:
       self.arch = 'i386'
-    self.pipe = None
-
-  def write_addr_to_pipe(self, offset):
-    print >> self.pipe.stdin, '0x%x' % int(offset, 16)
+    self.open_atos()
 
   def open_atos(self):
     if DEBUG:
       print 'atos -o %s -arch %s' % (self.binary, self.arch)
     cmdline = ['atos', '-o', self.binary, '-arch', self.arch]
-    self.pipe = subprocess.Popen(cmdline,
-                                 stdin=subprocess.PIPE,
-                                 stdout=subprocess.PIPE,
-                                 stderr=subprocess.PIPE)
+    self.atos = UnbufferedLineConverter(cmdline)
 
   def symbolize(self, addr, binary, offset):
     """Overrides Symbolizer.symbolize."""
     if self.binary != binary:
       return None
-    self.open_atos()
-    self.write_addr_to_pipe(offset)
-    self.pipe.stdin.close()
-    atos_line = self.pipe.stdout.readline().rstrip()
+    atos_line = self.atos.convert('0x%x' % int(offset, 16))
+    while "got symbolicator for" in atos_line:
+      atos_line = self.atos.readline()
     # A well-formed atos response looks like this:
     #   foo(type1, type2) (in object.name) (filename.cc:80)
     match = re.match('^(.*) \(in (.*)\) \((.*:\d*)\)$', atos_line)