Add ndk_missing_symbols.py.

ndk_missing_symbols.py pulls libc.so and libm.so off a running device
or emulator and shows the list of symbols that are in the current
bionic that aren't available on the target.

Change-Id: Ia92c315a6a0ce2e5c33db0b62c8fab41c08a4c31
diff --git a/libc/tools/check-symbols-glibc.py b/libc/tools/check-symbols-glibc.py
index 153b840..2c352b2 100755
--- a/libc/tools/check-symbols-glibc.py
+++ b/libc/tools/check-symbols-glibc.py
@@ -1,11 +1,27 @@
-#!/usr/bin/python
-
+#!/usr/bin/env python2
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# pylint: disable=bad-indentation,bad-continuation
 import glob
 import os
 import re
-import subprocess
 import sys
 
+import symbols
+
 only_unwanted = False
 if len(sys.argv) > 1:
   if sys.argv[1] in ('-u', '--unwanted'):
@@ -16,62 +32,18 @@
 if arch == 'aarch64':
   arch = 'arm64'
 
-def GetSymbolsFromTxt(txt_file):
-  symbols = set()
-  f = open(txt_file, 'r')
-  for line in f.read().splitlines():
-    symbols.add(line)
-  f.close()
-  return symbols
-
-def GetSymbolsFromSo(so_file):
-  # Example readelf output:
-  #   264: 0001623c     4 FUNC    GLOBAL DEFAULT    8 cabsf
-  #   266: 00016244     4 FUNC    GLOBAL DEFAULT    8 dremf
-  #   267: 00019018     4 OBJECT  GLOBAL DEFAULT   11 __fe_dfl_env
-  #   268: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_dcmplt
-
-  r = re.compile(r' +\d+: [0-9a-f]+ +\d+ (I?FUNC|OBJECT) +\S+ +\S+ +\d+ (\S+)')
-
-  symbols = set()
-
-  for line in subprocess.check_output(['readelf', '--dyn-syms', '-W', so_file]).split('\n'):
-    if ' HIDDEN ' in line or ' UND ' in line:
-      continue
-    m = r.match(line)
-    if m:
-      symbol = m.group(2)
-      symbol = re.sub('@.*', '', symbol)
-      symbols.add(symbol)
-
-  return symbols
-
-def GetSymbolsFromAndroidSo(*files):
-  symbols = set()
-  for f in files:
-    symbols = symbols | GetSymbolsFromSo('%s/system/lib64/%s' % (os.environ['ANDROID_PRODUCT_OUT'], f))
-  return symbols
-
-def GetSymbolsFromSystemSo(*files):
-  symbols = set()
-  for f in files:
-    f = glob.glob('/lib/x86_64-linux-gnu/%s' % f)[-1]
-    symbols = symbols | GetSymbolsFromSo(f)
-  return symbols
-
 def MangleGlibcNameToBionic(name):
   if name in glibc_to_bionic_names:
     return glibc_to_bionic_names[name]
   return name
 
-def GetNdkIgnored():
-  global arch
-  symbols = set()
+def GetNdkIgnored(arch):  # pylint: disable=redefined-outer-name
+  ignored_symbols = set()
   files = glob.glob('%s/ndk/build/tools/unwanted-symbols/%s/*' %
                     (os.getenv('ANDROID_BUILD_TOP'), arch))
   for f in files:
-    symbols |= set(open(f, 'r').read().splitlines())
-  return symbols
+    ignored_symbols |= set(open(f, 'r').read().splitlines())
+  return ignored_symbols
 
 glibc_to_bionic_names = {
   '__res_init': 'res_init',
@@ -81,10 +53,19 @@
   '__xpg_basename': '__gnu_basename',
 }
 
-glibc = GetSymbolsFromSystemSo('libc.so.*', 'librt.so.*', 'libpthread.so.*', 'libresolv.so.*', 'libm.so.*', 'libutil.so.*')
-bionic = GetSymbolsFromAndroidSo('libc.so', 'libm.so')
-posix = GetSymbolsFromTxt(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'posix-2013.txt'))
-ndk_ignored = GetNdkIgnored()
+glibc = symbols.GetFromSystemSo([
+    'libc.so.*',
+    'librt.so.*',
+    'libpthread.so.*',
+    'libresolv.so.*',
+    'libm.so.*',
+    'libutil.so.*',
+])
+
+bionic = symbols.GetFromAndroidSo(['libc.so', 'libm.so'])
+this_dir = os.path.dirname(os.path.realpath(__file__))
+posix = symbols.GetFromTxt(os.path.join(this_dir, 'posix-2013.txt'))
+ndk_ignored = GetNdkIgnored(arch)
 
 glibc = set(map(MangleGlibcNameToBionic, glibc))
 
diff --git a/libc/tools/ndk_missing_symbols.py b/libc/tools/ndk_missing_symbols.py
new file mode 100755
index 0000000..7b22ca8
--- /dev/null
+++ b/libc/tools/ndk_missing_symbols.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python2
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import os
+import subprocess
+import tempfile
+
+import symbols
+
+
+def adb_pull(src, dst):
+    with open(os.devnull, 'w') as devnull:
+        subprocess.check_call(['adb', 'pull', src, dst],
+                              stdout=devnull, stderr=devnull)
+
+
+def main():
+    tmp_dir = tempfile.mkdtemp()
+    adb_pull('/system/lib/libc.so', tmp_dir)
+    adb_pull('/system/lib/libm.so', tmp_dir)
+
+    current = symbols.GetFromAndroidSo(['libc.so', 'libm.so'])
+    device = (symbols.GetFromSo(os.path.join(tmp_dir, 'libc.so')) |
+              symbols.GetFromSo(os.path.join(tmp_dir, 'libm.so')))
+
+    for symbol in sorted(current - device):
+        print symbol
+
+
+if __name__ == '__main__':
+    main()
diff --git a/libc/tools/symbols.py b/libc/tools/symbols.py
new file mode 100644
index 0000000..43454e4
--- /dev/null
+++ b/libc/tools/symbols.py
@@ -0,0 +1,74 @@
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+import glob
+import os
+import re
+import subprocess
+
+
+def GetFromTxt(txt_file):
+    symbols = set()
+    f = open(txt_file, 'r')
+    for line in f.read().splitlines():
+        symbols.add(line)
+    f.close()
+    return symbols
+
+
+def GetFromSo(so_file):
+    # pylint: disable=line-too-long
+    # Example readelf output:
+    #     264: 0001623c         4 FUNC        GLOBAL DEFAULT        8 cabsf
+    #     266: 00016244         4 FUNC        GLOBAL DEFAULT        8 dremf
+    #     267: 00019018         4 OBJECT    GLOBAL DEFAULT     11 __fe_dfl_env
+    #     268: 00000000         0 FUNC        GLOBAL DEFAULT    UND __aeabi_dcmplt
+
+    r = re.compile(
+        r' +\d+: [0-9a-f]+ +\d+ (I?FUNC|OBJECT) +\S+ +\S+ +\d+ (\S+)')
+
+    symbols = set()
+
+    output = subprocess.check_output(['readelf', '--dyn-syms', '-W', so_file])
+    for line in output.split('\n'):
+        if ' HIDDEN ' in line or ' UND ' in line:
+            continue
+        m = r.match(line)
+        if m:
+            symbol = m.group(2)
+            symbol = re.sub('@.*', '', symbol)
+            symbols.add(symbol)
+
+    return symbols
+
+
+def GetFromAndroidSo(files):
+    out_dir = os.environ['ANDROID_PRODUCT_OUT']
+    lib_dir = os.path.join(out_dir, 'system/lib64')
+    if not os.path.isdir(lib_dir):
+        lib_dir = os.path.join(out_dir, 'system/lib')
+
+    results = set()
+    for f in files:
+        results |= GetFromSo(os.path.join(lib_dir, f))
+    return results
+
+
+def GetFromSystemSo(files):
+    lib_dir = '/lib/x86_64-linux-gnu'
+    results = set()
+    for f in files:
+        results |= GetFromSo(glob.glob(os.path.join(lib_dir, f))[-1])
+    return results