Merge "Revert^2 "Add vts_vndk_abi_test""
diff --git a/Android.bp b/Android.bp
index 176da18..7d6abec 100644
--- a/Android.bp
+++ b/Android.bp
@@ -51,6 +51,22 @@
 }
 
 python_test_host {
+    name: "vts_vndk_abi_test",
+    defaults: ["vts_vndk_default"],
+    main: "abi/vts_vndk_abi_test.py",
+    srcs: [
+        "abi/vts_vndk_abi_test.py",
+    ],
+    data: [
+        ":vts_vndk_abi_dump_zip",
+    ],
+    test_suites: [
+        "vts-core",
+    ],
+    test_config: "abi/vts_vndk_abi_test.xml",
+}
+
+python_test_host {
     name: "vts_vndk_dependency_test",
     defaults: ["vts_vndk_default"],
     main: "dependency/vts_vndk_dependency_test.py",
diff --git a/abi/vts_vndk_abi_test.py b/abi/vts_vndk_abi_test.py
new file mode 100644
index 0000000..56a5220
--- /dev/null
+++ b/abi/vts_vndk_abi_test.py
@@ -0,0 +1,361 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 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 json
+import logging
+import os
+import shutil
+import tempfile
+import unittest
+
+from vts.testcases.vndk import utils
+from vts.testcases.vndk.golden import vndk_data
+from vts.utils.python.library import elf_parser
+from vts.utils.python.library.vtable import vtable_dumper
+from vts.utils.python.vndk import vndk_utils
+
+
+class VtsVndkAbiTest(unittest.TestCase):
+    """A test module to verify ABI compliance of vendor libraries.
+
+    Attributes:
+        _dut: the AndroidDevice under test.
+        _temp_dir: The temporary directory for libraries copied from device.
+    """
+
+    def setUp(self):
+        """Initializes data file path, device, and temporary directory."""
+        serial_number = os.environ.get("ANDROID_SERIAL", "")
+        self.assertTrue(serial_number, "$ANDROID_SERIAL is empty")
+        self._dut = utils.AndroidDevice(serial_number)
+        self._temp_dir = tempfile.mkdtemp()
+
+    def tearDown(self):
+        """Deletes the temporary directory."""
+        logging.info("Delete %s", self._temp_dir)
+        shutil.rmtree(self._temp_dir)
+
+    def _PullOrCreateDir(self, target_dir, host_dir):
+        """Copies a directory from device. Creates an empty one if not exist.
+
+        Args:
+            target_dir: The directory to copy from device.
+            host_dir: The directory to copy to host.
+        """
+        if not self._dut.IsDirectory(target_dir):
+            logging.info("%s doesn't exist. Create %s.", target_dir, host_dir)
+            os.makedirs(host_dir)
+            return
+        parent_dir = os.path.dirname(host_dir)
+        if parent_dir and not os.path.isdir(parent_dir):
+            os.makedirs(parent_dir)
+        logging.info("adb pull %s %s", target_dir, host_dir)
+        self._dut.AdbPull(target_dir, host_dir)
+
+    def _ToHostPath(self, target_path):
+        """Maps target path to host path in self._temp_dir."""
+        return os.path.join(self._temp_dir, *target_path.strip("/").split("/"))
+
+    @staticmethod
+    def _LoadGlobalSymbolsFromDump(dump_obj):
+        """Loads global symbols from a dump object.
+
+        Args:
+            dump_obj: A dict, the dump in JSON format.
+
+        Returns:
+            A set of strings, the symbol names.
+        """
+        symbols = set()
+        for key in ("elf_functions", "elf_objects"):
+            symbols.update(
+                symbol.get("name", "") for symbol in dump_obj.get(key, []) if
+                symbol.get("binding", "global") == "global")
+        return symbols
+
+    def _DiffElfSymbols(self, dump_obj, parser):
+        """Checks if a library includes all symbols in a dump.
+
+        Args:
+            dump_obj: A dict, the dump in JSON format.
+            parser: An elf_parser.ElfParser that loads the library.
+
+        Returns:
+            A list of strings, the global symbols that are in the dump but not
+            in the library.
+
+        Raises:
+            elf_parser.ElfError if fails to load the library.
+        """
+        dump_symbols = self._LoadGlobalSymbolsFromDump(dump_obj)
+        lib_symbols = parser.ListGlobalDynamicSymbols(include_weak=True)
+        return sorted(dump_symbols.difference(lib_symbols))
+
+    @staticmethod
+    def _DiffVtableComponent(offset, expected_symbol, vtable):
+        """Checks if a symbol is in a vtable entry.
+
+        Args:
+            offset: An integer, the offset of the expected symbol.
+            expected_symbol: A string, the name of the expected symbol.
+            vtable: A dict of {offset: [entry]} where offset is an integer and
+                    entry is an instance of vtable_dumper.VtableEntry.
+
+        Returns:
+            A list of strings, the actual possible symbols if expected_symbol
+            does not match the vtable entry.
+            None if expected_symbol matches the entry.
+        """
+        if offset not in vtable:
+            return []
+
+        entry = vtable[offset]
+        if not entry.names:
+            return [hex(entry.value).rstrip('L')]
+
+        if expected_symbol not in entry.names:
+            return entry.names
+
+    def _DiffVtableComponents(self, dump_obj, dumper):
+        """Checks if a library includes all vtable entries in a dump.
+
+        Args:
+            dump_obj: A dict, the dump in JSON format.
+            dumper: An vtable_dumper.VtableDumper that loads the library.
+            bitness: 32 or 64, the size of the vtable entries.
+
+        Returns:
+            A list of tuples (VTABLE, OFFSET, EXPECTED_SYMBOL, ACTUAL).
+            ACTUAL can be "missing", a list of symbol names, or an ELF virtual
+            address.
+
+        Raises:
+            vtable_dumper.VtableError if fails to dump vtable from the library.
+        """
+        function_kinds = [
+            "function_pointer",
+            "complete_dtor_pointer",
+            "deleting_dtor_pointer"
+        ]
+        non_function_kinds = [
+            "vcall_offset",
+            "vbase_offset",
+            "offset_to_top",
+            "rtti",
+            "unused_function_pointer"
+        ]
+        default_vtable_component_kind = "function_pointer"
+
+        global_symbols = self._LoadGlobalSymbolsFromDump(dump_obj)
+
+        lib_vtables = {vtable.name: vtable
+                       for vtable in dumper.DumpVtables()}
+        logging.debug("\n\n".join(str(vtable)
+                                  for _, vtable in lib_vtables.items()))
+
+        vtables_diff = []
+        for record_type in dump_obj.get("record_types", []):
+            # Since Android R, unique_id has been replaced with linker_set_key.
+            # unique_id starts with "_ZTI"; linker_set_key starts with "_ZTS".
+            type_name_symbol = record_type.get("unique_id", "")
+            if type_name_symbol:
+                vtable_symbol = type_name_symbol.replace("_ZTS", "_ZTV", 1)
+            else:
+                type_name_symbol = record_type.get("linker_set_key", "")
+                vtable_symbol = type_name_symbol.replace("_ZTI", "_ZTV", 1)
+
+            # Skip if the vtable symbol isn't global.
+            if vtable_symbol not in global_symbols:
+                continue
+
+            # Collect vtable entries from library dump.
+            if vtable_symbol in lib_vtables:
+                lib_vtable = {entry.offset: entry
+                              for entry in lib_vtables[vtable_symbol].entries}
+            else:
+                lib_vtable = dict()
+
+            for index, entry in enumerate(record_type.get("vtable_components",
+                                                          [])):
+                entry_offset = index * dumper.bitness // 8
+                entry_kind = entry.get("kind", default_vtable_component_kind)
+                entry_symbol = entry.get("mangled_component_name", "")
+                entry_is_pure = entry.get("is_pure", False)
+
+                if entry_kind in non_function_kinds:
+                    continue
+
+                if entry_kind not in function_kinds:
+                    logging.warning("%s: Unexpected vtable entry kind %s",
+                                    vtable_symbol, entry_kind)
+
+                if entry_symbol not in global_symbols:
+                    # Itanium cxx abi doesn't specify pure virtual vtable
+                    # entry's behaviour. However we can still do some checks
+                    # based on compiler behaviour.
+                    # Even though we don't check weak symbols, we can still
+                    # issue a warning when a pure virtual function pointer
+                    # is missing.
+                    if entry_is_pure and entry_offset not in lib_vtable:
+                        logging.warning("%s: Expected pure virtual function"
+                                        "in %s offset %s",
+                                        vtable_symbol, vtable_symbol,
+                                        entry_offset)
+                    continue
+
+                diff_symbols = self._DiffVtableComponent(
+                    entry_offset, entry_symbol, lib_vtable)
+                if diff_symbols is None:
+                    continue
+
+                vtables_diff.append(
+                    (vtable_symbol, str(entry_offset), entry_symbol,
+                     (",".join(diff_symbols) if diff_symbols else "missing")))
+
+        return vtables_diff
+
+    def _ScanLibDirs(self, dump_zip, dump_paths, lib_dirs, dump_version):
+        """Compares dump files with libraries copied from device.
+
+        Args:
+            dump_zip: A zip_file.ZipFile object containing the dumps.
+            dump_paths: A dict of {library name: dump resource path}.
+            lib_dirs: The list of directories containing libraries.
+            dump_version: The VNDK version of the dump files. If the device has
+                          no VNDK version or has extension in vendor partition,
+                          this method compares the unversioned VNDK directories
+                          with the dump directories of the given version.
+
+        Returns:
+            A list of strings, the incompatible libraries.
+        """
+        error_list = []
+        lib_paths = dict()
+        for lib_dir in lib_dirs:
+            for parent_dir, dir_names, lib_names in os.walk(lib_dir):
+                for lib_name in lib_names:
+                    if lib_name not in lib_paths:
+                        lib_paths[lib_name] = os.path.join(parent_dir,
+                                                           lib_name)
+        for lib_name, dump_path in dump_paths.items():
+            if lib_name not in lib_paths:
+                logging.info("%s: Not found on target", lib_name)
+                continue
+            lib_path = lib_paths[lib_name]
+            rel_path = os.path.relpath(lib_path, self._temp_dir)
+
+            has_exception = False
+            missing_symbols = []
+            vtable_diff = []
+
+            try:
+                with dump_zip.open(dump_path, "r") as dump_file:
+                    dump_obj = json.load(dump_file)
+                with vtable_dumper.VtableDumper(lib_path) as dumper:
+                    missing_symbols = self._DiffElfSymbols(
+                        dump_obj, dumper)
+                    vtable_diff = self._DiffVtableComponents(
+                        dump_obj, dumper)
+            except (IOError,
+                    elf_parser.ElfError,
+                    vtable_dumper.VtableError) as e:
+                logging.exception("%s: Cannot diff ABI", rel_path)
+                has_exception = True
+
+            if missing_symbols:
+                logging.error("%s: Missing Symbols:\n%s",
+                              rel_path, "\n".join(missing_symbols))
+            if vtable_diff:
+                logging.error("%s: Vtable Difference:\n"
+                              "vtable offset expected actual\n%s",
+                              rel_path,
+                              "\n".join(" ".join(e) for e in vtable_diff))
+            if (has_exception or missing_symbols or vtable_diff):
+                error_list.append(rel_path)
+            else:
+                logging.info("%s: Pass", rel_path)
+        return error_list
+
+    @staticmethod
+    def _GetLinkerSearchIndex(target_path):
+        """Returns the key for sorting linker search paths."""
+        index = 0
+        for prefix in ("/odm", "/vendor", "/apex"):
+            if target_path.startswith(prefix):
+                return index
+            index += 1
+        return index
+
+    def _TestAbiCompatibility(self, bitness):
+        """Checks ABI compliance of VNDK libraries.
+
+        Args:
+            bitness: 32 or 64, the bitness of the tested libraries.
+        """
+        self.assertTrue(self._dut.IsRoot(), "This test requires adb root.")
+        primary_abi = self._dut.GetCpuAbiList()[0]
+        binder_bitness = self._dut.GetBinderBitness()
+        self.assertTrue(binder_bitness, "Cannot determine binder bitness.")
+        dump_version = self._dut.GetVndkVersion()
+        self.assertTrue(dump_version, "Cannot determine VNDK version.")
+
+        dump_paths = vndk_data.GetAbiDumpPathsFromResources(
+            dump_version,
+            binder_bitness,
+            primary_abi,
+            bitness)
+        self.assertTrue(
+            dump_paths,
+            "No dump files. version: %s ABI: %s bitness: %d" % (
+                dump_version, primary_abi, bitness))
+
+        target_dirs = vndk_utils.GetVndkExtDirectories(bitness)
+        target_dirs += vndk_utils.GetVndkSpExtDirectories(bitness)
+        target_dirs += [vndk_utils.GetVndkDirectory(bitness, dump_version)]
+        target_dirs.sort(key=self._GetLinkerSearchIndex)
+
+        host_dirs = [self._ToHostPath(x) for x in target_dirs]
+        for target_dir, host_dir in zip(target_dirs, host_dirs):
+            self._PullOrCreateDir(target_dir, host_dir)
+
+        with vndk_data.AbiDumpResource() as dump_resource:
+            assert_lines = self._ScanLibDirs(dump_resource.zip_file,
+                                             dump_paths, host_dirs,
+                                             dump_version)
+
+        if assert_lines:
+            error_count = len(assert_lines)
+            if error_count > 20:
+                assert_lines = assert_lines[:20] + ["..."]
+            assert_lines.append("Total number of errors: " + str(error_count))
+            self.fail("\n".join(assert_lines))
+
+    def testAbiCompatibility32(self):
+        """Checks ABI compliance of 32-bit VNDK libraries."""
+        self._TestAbiCompatibility(32)
+
+    def testAbiCompatibility64(self):
+        """Checks ABI compliance of 64-bit VNDK libraries."""
+        if self._dut.GetCpuAbiList(64):
+            self._TestAbiCompatibility(64)
+        else:
+            logging.info("Skip the test as the device doesn't support 64-bit "
+                         "ABI.")
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/abi/vts_vndk_abi_test.xml b/abi/vts_vndk_abi_test.xml
new file mode 100644
index 0000000..7fc7321
--- /dev/null
+++ b/abi/vts_vndk_abi_test.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<configuration description="Config for vts_vndk_abi_test">
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+    <test class="com.android.tradefed.testtype.python.PythonBinaryHostTest">
+        <option name="par-file-name" value="vts_vndk_abi_test" />
+        <option name="test-timeout" value="3m" />
+    </test>
+</configuration>
diff --git a/golden/Android.bp b/golden/Android.bp
index 20222e1..c9abdaa 100644
--- a/golden/Android.bp
+++ b/golden/Android.bp
@@ -26,3 +26,13 @@
         },
     }
 }
+
+// TODO(b/150663999): Replace with gensrcs when the build system is able to
+//                    process the large file group.
+genrule {
+    name: "vts_vndk_abi_dump_zip",
+    tools: ["extract_lsdump"],
+    cmd: "$(location extract_lsdump) $(in) $(out)",
+    srcs: [":vndk_abi_dump_zip"],
+    out: ["vts/testcases/vndk/abi_dump.zip"],
+}
diff --git a/golden/extract_lsdump.py b/golden/extract_lsdump.py
index a6d95c7..fd42be6 100755
--- a/golden/extract_lsdump.py
+++ b/golden/extract_lsdump.py
@@ -19,6 +19,7 @@
 import gzip
 import json
 import os
+import zipfile
 
 
 class LsdumpError(Exception):
@@ -205,6 +206,43 @@
         output_dump.record_types.append(record_type)
 
 
+def _ParseLsdumpFileObject(lsdump_file):
+    """Converts an lsdump file to a dump object.
+
+    Args:
+        lsdump_file: The file object of the input lsdump.
+
+    Returns:
+        The AttrDict object converted from the lsdump file.
+
+    Raises:
+        LsdumpError if fails to create the dump file.
+    """
+    try:
+        lsdump = json.load(lsdump_file, object_hook=AttrDict)
+    except ValueError:
+        raise LsdumpError(e)
+
+    try:
+        output_dump = AttrDict()
+        _ParseVtablesFromLsdump(lsdump, output_dump)
+        _ParseSymbolsFromLsdump(lsdump, output_dump)
+        return output_dump
+    except AttributeError as e:
+        raise LsdumpError(e)
+
+
+def _ParseLsdumpZipFile(lsdump_zip, output_zip):
+    """Converts zipped lsdump files to the dump files for ABI test."""
+    for name in lsdump_zip.namelist():
+        if name.endswith(".lsdump"):
+            with lsdump_zip.open(name, mode="r") as lsdump_file:
+                output_dump = _ParseLsdumpFileObject(lsdump_file)
+            output_str = json.dumps(output_dump, indent=1,
+                                    separators=(',', ':'))
+            output_zip.writestr(name, output_str)
+
+
 def ParseLsdumpFile(input_path, output_path):
     """Converts an lsdump file to a dump file for the ABI test.
 
@@ -215,25 +253,15 @@
     Raises:
         LsdumpError if fails to create the dump file.
     """
-    try:
-        with _OpenFileOrGzipped(input_path) as lsdump_file:
-            lsdump = json.load(lsdump_file, object_hook=AttrDict)
-    except (IOError, ValueError) as e:
-        raise LsdumpError(e)
-
-    try:
-        output_dump = AttrDict()
-        _ParseVtablesFromLsdump(lsdump, output_dump)
-        _ParseSymbolsFromLsdump(lsdump, output_dump)
-    except AttributeError as e:
-        raise LsdumpError(e)
-
     abs_output_path = os.path.abspath(output_path)
     abs_output_dir = os.path.dirname(abs_output_path)
 
     try:
         if abs_output_dir and not os.path.exists(abs_output_dir):
             os.makedirs(abs_output_dir)
+
+        with _OpenFileOrGzipped(input_path) as lsdump_file:
+            output_dump = _ParseLsdumpFileObject(lsdump_file)
         with open(output_path, 'wb') as output_file:
             json.dump(output_dump, output_file,
                       indent=1, separators=(',', ':'))
@@ -251,6 +279,17 @@
                             help='output dump file path.')
     args = arg_parser.parse_args()
 
+    # TODO(b/150663999): Remove this when the build system is able to process
+    #                    the large file group.
+    if zipfile.is_zipfile(args.input_path):
+        with zipfile.ZipFile(args.input_path, mode='r') as input_zip:
+            # The zip will be added to a Python package. It is not necessary
+            # to reduce the file size.
+            with zipfile.ZipFile(args.output_path, mode='w',
+                                 compression=zipfile.ZIP_STORED) as output_zip:
+                _ParseLsdumpZipFile(input_zip, output_zip)
+        exit(0)
+
     try:
         ParseLsdumpFile(args.input_path, args.output_path)
     except LsdumpError as e:
diff --git a/golden/vndk_data.py b/golden/vndk_data.py
index 4eb127d..ffa1527 100644
--- a/golden/vndk_data.py
+++ b/golden/vndk_data.py
@@ -15,9 +15,11 @@
 #
 
 import collections
+import json
 import logging
 import os
 import re
+import zipfile
 
 try:
     from importlib import resources
@@ -47,9 +49,16 @@
 # VNDK-SP dependencies that vendor modules cannot directly access.
 VNDK_SP_PRIVATE = "VNDK-SP-private"
 
-# The ABI dump directories. 64-bit comes before 32-bit in order to sequentially
-# search for longest prefix.
-_ABI_NAMES = ("arm64", "arm", "mips64", "mips", "x86_64", "x86")
+# The tuples of (ABI name, bitness, arch name). 64-bit comes before 32-bit in
+# order to sequentially search for longest prefix.
+_ABI_LIST = (
+    ("arm64", 64, "arm64_armv8-a"),
+    ("arm64", 32, "arm_armv8-a"),
+    ("arm", 32, "arm_armv7-a-neon"),
+    ("x86_64", 64, "x86_x86_64"),
+    ("x86_64", 32, "x86_64"),
+    ("x86", 32, "x86"),
+)
 
 # The data directory.
 _GOLDEN_DIR = os.path.join("vts", "testcases", "vndk", "golden")
@@ -57,6 +66,9 @@
 # The data package.
 _RESOURCE_PACKAGE = "vts.testcases.vndk";
 
+# The name of the zip file containing ABI dumps.
+_ABI_DUMP_ZIP_NAME = "abi_dump.zip"
+
 # Regular expression prefix for library name patterns.
 _REGEX_PREFIX = "[regex]"
 
@@ -95,7 +107,7 @@
         None if there is no directory for the version and ABI.
     """
     try:
-        abi_dir = next(x for x in _ABI_NAMES if abi_name.startswith(x))
+        abi_dir = next(x[0] for x in _ABI_LIST if abi_name.startswith(x[0]))
     except StopIteration:
         logging.warning("Unknown ABI %s.", abi_name)
         return None
@@ -117,6 +129,68 @@
     return dump_dir
 
 
+class AbiDumpResource:
+    """The class for loading ABI dumps from the zip in resources."""
+
+    def __init__(self):
+        self._resource = None
+        self.zip_file = None
+
+    def __enter__(self):
+        self._resource = resources.open_binary(_RESOURCE_PACKAGE,
+                                               _ABI_DUMP_ZIP_NAME)
+        self.zip_file = zipfile.ZipFile(self._resource, "r")
+        return self
+
+    def __exit__(self, exc_type, exc_val, traceback):
+        if self._resource:
+            self._resource.close()
+        if self.zip_file:
+            self.zip_file.close()
+
+
+def GetAbiDumpPathsFromResources(version, binder_bitness, abi_name, abi_bitness):
+    """Returns the VNDK dump paths in resources.
+
+    Args:
+        version: A string, the VNDK version.
+        binder_bitness: A string or an integer, 32 or 64.
+        abi_name: A string, the ABI of the library dump.
+        abi_bitness: A string or an integer, 32 or 64.
+
+    Returns:
+        A dict of {library name: dump resource path}. For example,
+        {"libbase.so": "R/64/arm64_armv8-a/source-based/libbase.so.lsdump"}.
+        If there is no dump for the version and ABI, this function returns an
+        empty dict.
+    """
+    if not resources:
+        logging.error("Could not import resources module.")
+        return dict()
+
+    abi_bitness = int(abi_bitness)
+    try:
+        arch_name = next(x[2] for x in _ABI_LIST if
+                         abi_name.startswith(x[0]) and x[1] == abi_bitness)
+    except StopIteration:
+        logging.warning("Unknown %d-bit ABI %s.", abi_bitness, abi_name)
+        return dict()
+
+    # The separator in zipped path is always "/".
+    dump_dir = "/".join((version, str(binder_bitness), arch_name,
+                         "source-based")) + "/"
+
+    dump_paths = dict()
+
+    with AbiDumpResource() as dump_resource:
+        for path in dump_resource.zip_file.namelist():
+            if path.startswith(dump_dir) and path.endswith(".lsdump"):
+                lib_name = path[len(dump_dir):-len(".lsdump")]
+                dump_paths[lib_name] = path
+
+    return dump_paths
+
+
 def _LoadVndkLibraryListsFile(vndk_lists, tags, vndk_lib_list_file):
     """Load VNDK libraries from the file to the specified tuple.
 
diff --git a/utils.py b/utils.py
index 38efdce..65b2eee 100644
--- a/utils.py
+++ b/utils.py
@@ -18,8 +18,11 @@
 # TODO(b/147454897): Keep the logic in sync with
 #                    test/vts/utils/python/controllers/android_device.py until
 #                    it is removed.
+import gzip
 import logging
+import os
 import subprocess
+import tempfile
 
 class AndroidDevice(object):
     """This class controls the device via adb commands."""
@@ -141,6 +144,51 @@
         """Gets the VNDK version that the vendor partition requests."""
         return self._GetProp("ro.vndk.version")
 
+    def GetKernelConfig(self, config_name):
+        """Gets kernel config from the device.
+
+        Args:
+            config_name: A string, the name of the configuration.
+
+        Returns:
+            "y" or "m" if the config is set.
+            "" if the config is not set.
+            None if fails to read config.
+        """
+        line_prefix = config_name + "="
+        with tempfile.NamedTemporaryFile(delete=False) as temp_file:
+            config_path = temp_file.name
+        try:
+            logging.debug("Pull config.gz to %s", config_path)
+            self.AdbPull("/proc/config.gz", config_path)
+            with gzip.open(config_path, "rt") as config_file:
+                for line in config_file:
+                    if line.strip().startswith(line_prefix):
+                        logging.debug("Found config: %s", line)
+                        return line.strip()[len(line_prefix):]
+            logging.debug("%s is not set.", config_name)
+            return ""
+        except (subprocess.CalledProcessError, IOError) as e:
+            logging.exception("Cannot read kernel config.", e)
+            return None
+        finally:
+            os.remove(config_path)
+
+    def GetBinderBitness(self):
+        """Returns the value of BINDER_IPC_32BIT in kernel config.
+
+        Returns:
+            32 or 64, binder bitness of the device.
+            None if fails to read config.
+        """
+        config_value = self.GetKernelConfig("CONFIG_ANDROID_BINDER_IPC_32BIT")
+        if config_value is None:
+            return None
+        elif config_value:
+            return 32
+        else:
+            return 64
+
     def IsRoot(self):
         """Returns whether adb has root privilege on the device."""
         out, err, return_code = self.Execute("id")